/*-------------------------------------------------------------------------
 *
 * fd.c
 *	  虚拟文件描述符代码。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/storage/file/fd.c
 *
 * NOTES:
 *
 * 本代码管理“虚拟”文件描述符（VFD）的缓存。
 * 服务器因各种原因打开多个文件描述符，包括基本表、临时文件（例如，排序和哈希缓冲文件），
 * 以及随机调用C库例程，如system(3)。单个进程的打开文件数很容易超过系统限制。
 * （在许多现代操作系统中，这个限制约为1024，但在其他系统中可能更低。）
 *
 * VFD被作为LRU池来管理，实际的操作系统文件描述符会根据需要打开和关闭。
 * 显然，如果使用这些接口打开了一个例程，则所有后续操作也必须通过这些接口进行
 * （File类型并不是真正的文件描述符）。
 *
 * 为了使这个方案有效，服务器中大多数（如果不是全部）例程应该使用这些接口，而不是直接调用C库例程
 * （例如，open(2)和fopen(3)）。否则，我们可能会发现实际的文件描述符还是不够用。
 *
 * 接口例程
 *
 * PathNameOpenFile和OpenTemporaryFile用于打开虚拟文件。
 * 使用OpenTemporaryFile打开的File在显式或隐式关闭时会自动删除，
 * 无论是在事务结束还是进程退出时。PathNameOpenFile用于长时间保持打开的文件，
 * 例如关系文件。关闭这些文件的责任在于调用者，fd.c中没有自动机制来处理这一点。
 *
 * PathName(Create|Open|Delete)Temporary(File|Dir)用于管理具有名称的临时文件，
 * 以便在后端之间共享。这些文件会自动关闭，并且会计入创建它们的后端的临时文件限制，
 * 但与匿名文件不同，它们不会自动删除。有关共享文件的自动清理机制，请参见sharedfileset.c。
 *
 * AllocateFile、AllocateDir、OpenPipeStream和OpenTransientFile是对fopen(3)、opendir(3)、
 * popen(3)和open(2)的封装。它们的行为类似于对应的本地函数，除了句柄会随
 * 当前子事务注册，并将在中止时自动关闭。这些主要用于短时间操作，如读取配置文件；
 * 使用这些函数在任何时刻可以打开的文件数量是有限制的。
 *
 * 最后，BasicOpenFile只是对open()的薄包装，可以在必要时释放由虚拟文件描述符占用的文件描述符。
 * BasicOpenFile返回的文件描述符没有自动清理，调用者必须负责通过调用close(2)来关闭文件描述符。
 *
 * 如果需要持有非虚拟文件描述符一段时间，请通过调用AcquireExternalFD或ReserveExternalFD
 * （最终调用ReleaseExternalFD）向fd.c报告，以便我们在决定可以打开的VFD数量时考虑到它。
 * 这适用于使用BasicOpenFile获得的FD以及未使用任何fd.c API获得的FD。
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include <dirent.h>
#include <sys/file.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
#ifndef WIN32
#include <sys/mman.h>
#endif
#include <limits.h>
#include <unistd.h>
#include <fcntl.h>
#ifdef HAVE_SYS_RESOURCE_H
#include <sys/resource.h>		/* 用于 getrlimit */
#endif

#include "access/xact.h"
#include "access/xlog.h"
#include "catalog/pg_tablespace.h"
#include "common/file_perm.h"
#include "common/file_utils.h"
#include "common/pg_prng.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "port/pg_iovec.h"
#include "portability/mem.h"
#include "postmaster/startup.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "utils/guc.h"
#include "utils/resowner_private.h"

/* 如果我们有 pg_flush_data 的实现，定义 PG_FLUSH_DATA_WORKS */
#if defined(HAVE_SYNC_FILE_RANGE)
#define PG_FLUSH_DATA_WORKS 1
#elif !defined(WIN32) && defined(MS_ASYNC)
#define PG_FLUSH_DATA_WORKS 1
#elif defined(USE_POSIX_FADVISE) && defined(POSIX_FADV_DONTNEED)
#define PG_FLUSH_DATA_WORKS 1
#endif

/*
 * 我们必须留下一些文件描述符供 system()、动态加载器和其他尝试打开文件而不咨询 fd.c 的代码使用。这是留出来的数量。（虽然我们尽力防止 EMFILE 错误，但没有任何保证我们不会因为其他进程消耗文件描述符而遭遇 ENFILE。因此，未经 fd.c 咨询就尝试打开文件是个坏主意。尽管如此，我们无法控制所有代码。）
 *
 * 由于这只是一个固定设置，我们实际上假设没有这样的代码会长期保持文件描述符打开；否则，剩余的数量可能不足。特别要注意的是，我们期望加载共享库不会导致打开文件的数量永久增加。（截至 2004 年 2 月，这在大多数甚至所有平台上似乎都属实。）
 */
#define NUM_RESERVED_FDS		10

/*
 * 在考虑保留的文件描述符后，如果可用的文件描述符少于此数量，则发出警告。（这个值的选择是为了与 "ulimit -n 64" 配合使用，但不能少于这个值太多。注意，这个值确保 numExternalFDs 至少可以为 16；截至当前写作，contrib/postgres_fdw 回归测试将在该值至少能够增长到 14 之前无法通过。）
 */
#define FD_MINFREE				48

/*
 * 许多平台允许单个进程打开比它们实际上可以支持的更多文件，尤其是在*许多*进程这样做时。
 * 这个 GUC 参数允许数据库管理员将 max_safe_fds 限制为小于 postmaster 的初始探测建议的值。
 */
int			max_files_per_process = 1000;

/*
 * fd.c 知道的操作可以打开的最大文件描述符数量（VFD、AllocateFile 等，或“外部”文件描述符）。该值初始化为保守值，并在引导或独立后端案例中无限期保持此值。在正常的 postmaster 操作中，postmaster 会在初始化后期调用 set_max_safe_fds() 来更新该值，然后该值会被派生的子进程继承。
 *
 * 注意：在设置这个变量时，会考虑 max_files_per_process 的值，因此无需单独测试。
 */
int			max_safe_fds = FD_MINFREE;	/* 默认值如果没有更改 */

/* 在 fsync() 失败后是否安全继续运行。 */
bool		data_sync_retry = false;

/* SyncDataDirectory() 应该如何执行其工作。 */
int			recovery_init_sync_method = RECOVERY_INIT_SYNC_METHOD_FSYNC;

/* 调试.... */

#ifdef FDDEBUG
#define DO_DB(A) \
	do { \
		int			fc__do_db_save_errno = errno; \
		A; \
		errno = fc__do_db_save_errno; \
	} while (0)
#else
#define DO_DB(A) \
	((void) 0)
#endif

#define VFD_CLOSED (-1)

#define FileIsValid(file) \
	((file) > 0 && (file) < (int) SizeVfdCache && VfdCache[file].fileName != NULL)

#define FileIsNotOpen(file) (VfdCache[file].fd == VFD_CLOSED)

/* 这些是下面 fdstate 中分配的位： */
#define FD_DELETE_AT_CLOSE	(1 << 0)	/* T = 关闭时删除 */
#define FD_CLOSE_AT_EOXACT	(1 << 1)	/* T = 在 eoXact 时关闭 */
#define FD_TEMP_FILE_LIMIT	(1 << 2)	/* T = 尊重 temp_file_limit */

typedef struct vfd
{
	int			fd;				/* 当前文件描述符，或 VFD_CLOSED 如果没有 */
	unsigned short fdstate;		/* VFD 状态的位标志 */
	ResourceOwner resowner;		/* 所有者，用于自动清理 */
	File		nextFree;		/* 指向下一个空闲 VFD 的链接，如果在空闲列表中 */
	File		lruMoreRecently;	/* 双向链接的使用频率排列表 */
	File		lruLessRecently;
	off_t		fileSize;		/* 文件的当前大小（如果不是临时文件，则为 0） */
	char	   *fileName;		/* 文件名，或 NULL 表示未使用的 VFD */
	/* 注意：fileName 是 malloc 的，并且在关闭 VFD 时必须释放 */
	int			fileFlags;		/* 用于（重新）打开文件的 open(2) 标志 */
	mode_t		fileMode;		/* 传递给 open(2) 的模式 */
} Vfd;

/*
 * 虚拟文件描述符数组指针和大小。它会根据需要增长。'File' 值是该数组中的索引。
 * 注意，VfdCache[0] 不是可用的 VFD，只是一个列表头。
 */
static Vfd *VfdCache;
static Size SizeVfdCache = 0;

/*
 * 已知被 VFD 条目使用的文件描述符数量。
 */
static int	nfile = 0;

/*
 * 标志用于指示扫描 VfdCache 查找临时文件以关闭是否值得
 */
static bool have_xact_temporary_files = false;

/*
 * 跟踪所有临时文件的总大小。注意：当 enforcing temp_file_limit 时，这不会溢出，因为限制不能超过 INT_MAX 千字节。在不执行强制时，它理论上可能会溢出，但我们不关心。
 */
static uint64 temporary_files_size = 0;

/* 临时文件访问已初始化且尚未关闭？ */
#ifdef USE_ASSERT_CHECKING
static bool temporary_files_allowed = false;
#endif

/*
 * 使用 AllocateFile、AllocateDir 和 OpenTransientFile 打开的操作系统句柄列表。
 */
typedef enum
{
	AllocateDescFile,
	AllocateDescPipe,
	AllocateDescDir,
	AllocateDescRawFD
} AllocateDescKind;

typedef struct
{
	AllocateDescKind kind;
	SubTransactionId create_subid;
	union
	{
		FILE	   *file;
		DIR		   *dir;
		int			fd;
	}			desc;
} AllocateDesc;

static int	numAllocatedDescs = 0;
static int	maxAllocatedDescs = 0;
static AllocateDesc *allocatedDescs = NULL;

/*
 * 报告给 Reserve/ReleaseExternalFD 的开放“外部”文件描述符数量。
 */
static int	numExternalFDs = 0;

/*
 * 当前会话中打开的临时文件数量；
 * 这用于生成临时文件名。
 */
static long tempFileCounter = 0;

/*
 * 临时表空间的 OID 数组。 （一些条目可能是 InvalidOid，表示应该使用当前数据库的默认表空间。）
 * 当 numTempTableSpaces 为 -1 时，表示在当前事务中尚未设置。
 */
static Oid *tempTableSpaces = NULL;
static int	numTempTableSpaces = -1;
static int	nextTempTableSpace = 0;


/*--------------------
 *
 * 私有例程
 *
 * Delete           - 从 Lru 环形链表中删除一个文件
 * LruDelete        - 从 Lru 环形链表中移除一个文件并关闭其 FD
 * Insert           - 将一个文件放到 Lru 环形链表的前面
 * LruInsert        - 将一个文件放到 Lru 环形链表的前面并打开它
 * ReleaseLruFile  - 通过关闭 Lru 环形链表中的最后一个条目来释放一个 fd
 * ReleaseLruFiles - 释放 fd(s)，直到我们低于 max_safe_fds 限制
 * AllocateVfd     - 获取一个空闲（或新的）文件记录（来自 VfdCache）
 * FreeVfd         - 释放一个文件记录
 *
 * 最少使用环是一种双向链表，它从元素零开始并结束。 元素零是特殊的——它不代表一个文件，其 "fd" 字段始终 == VFD_CLOSED。 元素零只是一个锚点，显示我们环的开始和结束。
 * 只有当前真实打开的 VFD 元素（已分配 FD 的）才在 Lru 环形链表中。 “虚拟”打开的元素可以通过其非空的 fileName 字段来识别。
 *
 * 示例：
 *
 *	   /--less----\				   /---------\
 *	   v		   \			  v			  \
 *	 #0 --more---> LeastRecentlyUsed --more-\ \
 *	  ^\									| |
 *	   \\less--> MostRecentlyUsedFile	<---/ |
 *		\more---/					 \--less--/
 *
 *--------------------
 */
static void fc_Delete(File fc_file);
static void fc_LruDelete(File fc_file);
static void fc_Insert(File fc_file);
static int	fc_LruInsert(File fc_file);
static bool fc_ReleaseLruFile(void);
static void fc_ReleaseLruFiles(void);
static File fc_AllocateVfd(void);
static void fc_FreeVfd(File fc_file);

static int	fc_FileAccess(File fc_file);
static File fc_OpenTemporaryFileInTablespace(Oid fc_tblspcOid, bool fc_rejectError);
static bool fc_reserveAllocatedDesc(void);
static int	fc_FreeDesc(AllocateDesc *fc_desc);

static void fc_BeforeShmemExit_Files(int fc_code, Datum fc_arg);
static void fc_CleanupTempFiles(bool fc_isCommit, bool fc_isProcExit);
static void fc_RemovePgTempRelationFiles(const char *fc_tsdirname);
static void fc_RemovePgTempRelationFilesInDbspace(const char *fc_dbspacedirname);

static void fc_walkdir(const char *fc_path,
					void (*fc_action) (const char *fname, bool isdir, int elevel),
					bool fc_process_symlinks,
					int fc_elevel);
#ifdef PG_FLUSH_DATA_WORKS
static void fc_pre_sync_fname(const char *fc_fname, bool fc_isdir, int fc_elevel);
#endif
static void fc_datadir_fsync_fname(const char *fc_fname, bool fc_isdir, int fc_elevel);
static void fc_unlink_if_exists_fname(const char *fc_fname, bool fc_isdir, int fc_elevel);

static int	fc_fsync_parent_path(const char *fc_fname, int fc_elevel);


/*
 * pg_fsync --- 执行带或不带写穿的 fsync
 */
int pg_fsync(int fc_fd)
{
#if !defined(WIN32) && defined(USE_ASSERT_CHECKING)
	struct stat fc_st;

	/*
	 * 一些操作系统对 fsync() 的实现对打开文件描述符时使用的文件访问模式有要求，这些要求取决于文件描述符是否用于目录。
	 *
	 * 对于任何可能最终传递给 fsync() 的文件描述符，我们应该以与所有支持的系统的 fsync() 兼容的访问模式打开它，否则代码可能不具可移植性，即使它在当前系统上运行良好。
	 *
	 * 我们在此断言文件的描述符是以写权限（O_RDWR 或 O_WRONLY）打开的，而目录则是以无写权限（O_RDONLY）打开的。
	 *
	 * 忽略任何 fstat 错误，让后续的 fsync() 执行其工作。
	 * 在这里进行此合理性检查的目的是考虑到 fsync() 被禁用的情况。
	 */
	if (fstat(fc_fd, &fc_st) == 0)
	{
		int			fc_desc_flags = fcntl(fc_fd, F_GETFL);

		/*
		 * O_RDONLY 在历史上是 0，因此只需确保目录没有使用写标志。
		 */
		if (S_ISDIR(fc_st.st_mode))
			Assert((fc_desc_flags & (O_RDWR | O_WRONLY)) == 0);
		else
			Assert((fc_desc_flags & (O_RDWR | O_WRONLY)) != 0);
	}
	errno = 0;
#endif

	/* #if 用于跳过 sync_method 测试（如果不需要） */
#if defined(HAVE_FSYNC_WRITETHROUGH) && !defined(FSYNC_WRITETHROUGH_IS_FSYNC)
	if (sync_method == SYNC_METHOD_FSYNC_WRITETHROUGH)
		return pg_fsync_writethrough(fc_fd);
	else
#endif
		return pg_fsync_no_writethrough(fc_fd);
}


/*
 * pg_fsync_no_writethrough --- 与 fsync 相同，仅在 enableFsync 为关闭时不执行任何操作
 */
int pg_fsync_no_writethrough(int fc_fd)
{
	if (enableFsync)
		return fsync(fc_fd);
	else
		return 0;
}

/*
 * pg_fsync_writethrough
 */
int pg_fsync_writethrough(int fc_fd)
{
	if (enableFsync)
	{
#ifdef WIN32
		return _commit(fd);
#elif defined(F_FULLFSYNC)
		return (fcntl(fd, F_FULLFSYNC, 0) == -1) ? -1 : 0;
#else
		errno = ENOSYS;
		return -1;
#endif
	}
	else
		return 0;
}

/*
 * pg_fdatasync --- 与 fdatasync 相同，仅在 enableFsync 为关闭时不执行任何操作
 *
 * 并非所有平台都具有 fdatasync；如果不可用，则视为 fsync。
 */
int pg_fdatasync(int fc_fd)
{
	if (enableFsync)
	{
#ifdef HAVE_FDATASYNC
		return fdatasync(fc_fd);
#else
		return fsync(fc_fd);
#endif
	}
	else
		return 0;
}

/*
 * pg_flush_data --- 通知操作系统应刷新描述的脏数据
 *
 * 偏移量为 0，字节数为 0 表示应刷新整个文件
 */
void pg_flush_data(int fc_fd, off_t fc_offset, off_t fc_nbytes)
{
	/*
	 * 现在，文件刷新主要用于避免后续的 fsync()/fdatasync() 调用产生较小的影响。因此，如果 fsync 被禁用，则不要触发刷新 - 这是我们可能希望在某个时刻进行配置的决定。
	 */
	if (!enableFsync)
		return;

	/*
	 * 我们编译当前平台上支持的所有替代方案，以便更容易找到可移植性问题。
	 */
#if defined(HAVE_SYNC_FILE_RANGE)
	{
		int			fc_rc;
		static bool fc_not_implemented_by_kernel = false;

		if (fc_not_implemented_by_kernel)
			return;

		/*
		 * sync_file_range(SYNC_FILE_RANGE_WRITE)，当前特定于 linux，
		 * 告诉操作系统应开始指定块的写回，但我们不想等待完成。 请注意，如果范围内存在过多的脏数据，此调用可能会阻塞。
		 * 这是在支持的操作系统上首选的方法，因为在可用时它可靠工作（与 msync() 对比）并且不清除干净数据（如 FADV_DONTNEED）。
		 */
		fc_rc = sync_file_range(fc_fd, fc_offset, fc_nbytes,
							 SYNC_FILE_RANGE_WRITE);
		if (fc_rc != 0)
		{
			int			fc_elevel;

			/*
			 * 对于没有 sync_file_range() 实现的系统，如 Windows WSL，仅生成一个警告，然后抑制该过程的所有进一步尝试。
			 */
			if (errno == ENOSYS)
			{
				fc_elevel = WARNING;
				fc_not_implemented_by_kernel = true;
			}
			else
				fc_elevel = data_sync_elevel(WARNING);

			ereport(fc_elevel,
					(errcode_for_file_access(),
					 errmsg("could not flush dirty data: %m")));
		}

		return;
	}
#endif
#if !defined(WIN32) && defined(MS_ASYNC)
	{
		void	   *fc_p;
		static int	fc_pagesize = 0;

		/*
		 * 在一些操作系统上，mmap 文件上的 msync(MS_ASYNC) 触发写回。 在 linux 中，只有在指定 MS_SYNC 时才会这样做，但随后会同步地执行写回。 幸运的是，所有常见的 linux 系统都有 sync_file_range()。 这比 FADV_DONTNEED 更可取，因为它不会清除干净数据。
		 *
		 * 我们对文件进行映射 (mmap())，告诉内核同步回内容 (msync())，然后再移除映射 (munmap())。
		 */

		/* mmap() 需要实际长度，如果我们想映射整个文件 */
		if (fc_offset == 0 && fc_nbytes == 0)
		{
			fc_nbytes = lseek(fc_fd, 0, SEEK_END);
			if (fc_nbytes < 0)
			{
				ereport(WARNING,
						(errcode_for_file_access(),
						 errmsg("could not determine dirty data size: %m")));
				return;
			}
		}

		
/*
		 * 一些平台会拒绝部分页面 mmap() 尝试。为了解决
		 * 这个问题，只需将请求截断到页面边界。如果有任何额外的
		 * 字节未被刷新，好吧，这只是一个提示而已。
		 */

		/* 仅获取一次页面大小 */
		if (fc_pagesize == 0)
			fc_pagesize = sysconf(_SC_PAGESIZE);

		/* 将长度对齐到页面大小，舍去任何小数页面 */
		if (fc_pagesize > 0)
			fc_nbytes = (fc_nbytes / fc_pagesize) * fc_pagesize;

		/* 小数页面请求是一个无操作 */
		if (fc_nbytes <= 0)
			return;

		/*
		 * mmap 可能会失败，特别是在 32 位平台上，那里可能
		 * 根本没有足够的地址空间。如果是这样，则静默地跳到
		 * 下一个实现。
		 */
		if (fc_nbytes <= (off_t) SSIZE_MAX)
			fc_p = mmap(NULL, fc_nbytes, PROT_READ, MAP_SHARED, fc_fd, fc_offset);
		else
			fc_p = MAP_FAILED;

		if (fc_p != MAP_FAILED)
		{
			int			fc_rc;

			fc_rc = msync(fc_p, (size_t) fc_nbytes, MS_ASYNC);
			if (fc_rc != 0)
			{
				ereport(data_sync_elevel(WARNING),
						(errcode_for_file_access(),
						 errmsg("could not flush dirty data: %m")));
				/* 注意：需要跳到 munmap()！ */
			}

			fc_rc = munmap(fc_p, (size_t) fc_nbytes);
			if (fc_rc != 0)
			{
				/* 致命错误，因为映射将保持 */
				ereport(FATAL,
						(errcode_for_file_access(),
						 errmsg("could not munmap() while flushing data: %m")));
			}

			return;
		}
	}
#endif
#if defined(USE_POSIX_FADVISE) && defined(POSIX_FADV_DONTNEED)
	{
		int			fc_rc;

		/*
		 * 向内核发出信号，指示传入的范围不再应该被缓存。
		 * 这会产生一个期望中的副作用，即写出脏数据，以及一个
		 * 不期望的副作用，即可能丢弃有用的干净缓存块。
		 * 基于后者的原因，这不是最理想的方法。
		 */

		fc_rc = posix_fadvise(fc_fd, fc_offset, fc_nbytes, POSIX_FADV_DONTNEED);

		if (fc_rc != 0)
		{
			/* 不要报错，这只是一个性能优化 */
			ereport(WARNING,
					(errcode_for_file_access(),
					 errmsg("could not flush dirty data: %m")));
		}

		return;
	}
#endif
}

/*
 * 通过名称将文件截断为给定长度。
 */
int pg_truncate(const char *fc_path, off_t fc_length)
{
#ifdef WIN32
	int			save_errno;
	int			ret;
	int			fd;

	fd = OpenTransientFile(path, O_RDWR | PG_BINARY);
	if (fd >= 0)
	{
		ret = ftruncate(fd, length);
		save_errno = errno;
		CloseTransientFile(fd);
		errno = save_errno;
	}
	else
		ret = -1;

	return ret;
#else
	return truncate(fc_path, fc_length);
#endif
}

/*
 * fsync_fname -- 对文件或目录执行 fsync，正确处理错误
 *
 * 尝试对文件或目录执行 fsync。当处理目录时，忽略指示
 * 操作系统根本不允许/要求对目录进行 fsync 的错误。
 */
void fsync_fname(const char *fc_fname, bool fc_isdir)
{
	fsync_fname_ext(fc_fname, fc_isdir, false, data_sync_elevel(ERROR));
}

/*
 * durable_rename -- rename(2) 包装器，发出确保持久性的 fsync
 *
 * 该例程确保在返回后，重命名文件的效果在发生崩溃时仍然
 * 保持。该例程运行时的崩溃将使您拥有预先存在的文件或
 * 移动后的文件，而不会出现混合状态或截断文件的情况。
 *
 * 它通过在重命名之前对旧文件名和可能存在的目标文件名
 * 使用 fsync，在重命名之后对目标文件和目录使用 fsync 来实现。
 *
 * 请注意，rename() 不能跨任意目录使用，因为它们可能不在
 * 同一文件系统上。因此该例程不支持跨目录重命名。
 *
 * 记录错误，记录者指定严重性。
 *
 * 如果操作成功则返回 0，其他情况返回 -1。注意，返回时
 * errno 是无效的。
 */
int durable_rename(const char *fc_oldfile, const char *fc_newfile, int fc_elevel)
{
	int			fc_fd;

	/*
	 * 首先对旧路径和目标路径（如果存在）进行 fsync，以确保它们在磁盘上是持久的。同步目标文件并不是绝对必要的，但这使得在崩溃时更容易推理；因为这时可以保证在崩溃后源文件或目标文件至少存在一个。
	 */
	if (fsync_fname_ext(fc_oldfile, false, false, fc_elevel) != 0)
		return -1;

	fc_fd = OpenTransientFile(fc_newfile, PG_BINARY | O_RDWR);
	if (fc_fd < 0)
	{
		if (errno != ENOENT)
		{
			ereport(fc_elevel,
					(errcode_for_file_access(),
					 errmsg("could not open file \"%s\": %m", fc_newfile)));
			return -1;
		}
	}
	else
	{
		if (pg_fsync(fc_fd) != 0)
		{
			int			fc_save_errno;

			/* 在出错时关闭文件，可能不在事务上下文中 */
			fc_save_errno = errno;
			CloseTransientFile(fc_fd);
			errno = fc_save_errno;

			ereport(fc_elevel,
					(errcode_for_file_access(),
					 errmsg("could not fsync file \"%s\": %m", fc_newfile)));
			return -1;
		}

		if (CloseTransientFile(fc_fd) != 0)
		{
			ereport(fc_elevel,
					(errcode_for_file_access(),
					 errmsg("could not close file \"%s\": %m", fc_newfile)));
			return -1;
		}
	}

	/* 该是做真正的事情的时候了... */
	if (rename(fc_oldfile, fc_newfile) < 0)
	{
		ereport(fc_elevel,
				(errcode_for_file_access(),
				 errmsg("could not rename file \"%s\" to \"%s\": %m",
						fc_oldfile, fc_newfile)));
		return -1;
	}

	/*
	 * 为了保证重命名文件是持久的，对其新名称的文件及其包含的目录进行 fsync。
	 */
	if (fsync_fname_ext(fc_newfile, false, false, fc_elevel) != 0)
		return -1;

	if (fc_fsync_parent_path(fc_newfile, fc_elevel) != 0)
		return -1;

	return 0;
}

/*
 * durable_unlink -- 以持久的方式删除文件
 *
 * 该例程确保在返回后，删除文件的效果在发生崩溃时仍然
 * 保持。该例程运行时的崩溃将使系统不处于混合状态。
 *
 * 它通过在实际删除完成后对文件的父目录使用 fsync 来实现。
 *
 * 记录错误，记录者指定严重性。
 *
 * 如果操作成功则返回 0，其他情况返回 -1。注意，返回时
 * errno 是无效的。
 */
int durable_unlink(const char *fc_fname, int fc_elevel)
{
	if (unlink(fc_fname) < 0)
	{
		ereport(fc_elevel,
				(errcode_for_file_access(),
				 errmsg("could not remove file \"%s\": %m",
						fc_fname)));
		return -1;
	}

	/*
	 * 为确保文件的删除是持久的，需对其父目录执行 fsync。
	 */
	if (fc_fsync_parent_path(fc_fname, fc_elevel) != 0)
		return -1;

	return 0;
}

/*
 * durable_rename_excl -- 以持久的方式重命名文件。
 *
 * 类似于 durable_rename()，但该例程尝试（但不保证）不覆盖目标文件。
 *
 * 注意，在不幸的时刻崩溃可能会导致你拥有两个指向目标文件的链接。
 *
 * 记录具有调用方指定严重性的错误。
 *
 * 在 Windows 上，使用硬链接后跟 unlink() 会导致并发问题，而简单的 rename() 不会产生这种问题，因此在更改此例程的逻辑时请小心。
 *
 * 如果操作成功则返回 0，否则返回 -1。注意 errno 在返回时无效。
 */
int durable_rename_excl(const char *fc_oldfile, const char *fc_newfile, int fc_elevel)
{
	/*
	 * 确保如果在重命名/链接后立即崩溃，能够将带有有效内容的文件移动到位。
	 */
	if (fsync_fname_ext(fc_oldfile, false, false, fc_elevel) != 0)
		return -1;

#ifdef HAVE_WORKING_LINK
	if (link(fc_oldfile, fc_newfile) < 0)
	{
		ereport(fc_elevel,
				(errcode_for_file_access(),
				 errmsg("could not link file \"%s\" to \"%s\": %m",
						fc_oldfile, fc_newfile)));
		return -1;
	}
	unlink(fc_oldfile);
#else
	if (rename(fc_oldfile, fc_newfile) < 0)
	{
		ereport(elevel,
				(errcode_for_file_access(),
				 errmsg("could not rename file \"%s\" to \"%s\": %m",
						fc_oldfile, fc_newfile)));
		return -1;
	}
#endif

	/*
	 * 在操作系统崩溃的情况下，确保变更持久化，新的条目及其父目录都需要刷新。
	 */
	if (fsync_fname_ext(fc_newfile, false, false, fc_elevel) != 0)
		return -1;

	/* 对父目录同样适用 */
	if (fc_fsync_parent_path(fc_newfile, fc_elevel) != 0)
		return -1;

	return 0;
}

/*
 * InitFileAccess --- 在后端启动期间初始化此模块
 *
 * 该模块在正常或独立后端启动期间调用。
 * 它*不是*在 postmaster 中调用。
 *
 * 请注意，这并不初始化临时文件访问，这通过 InitTemporaryFileAccess() 单独初始化。
 */
void InitFileAccess(void)
{
	Assert(SizeVfdCache == 0);	/* 只调用我一次 */

	/* 初始化缓存头条目 */
	VfdCache = (Vfd *) malloc(sizeof(Vfd));
	if (VfdCache == NULL)
		ereport(FATAL,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of memory")));

	MemSet((char *) &(VfdCache[0]), 0, sizeof(Vfd));
	VfdCache->fd = VFD_CLOSED;

	SizeVfdCache = 1;
}

/*
 * InitTemporaryFileAccess --- 在启动期间初始化临时文件访问
 *
 * 该模块在正常或独立后端启动期间调用。
 * 它*不是*在 postmaster 中调用。
 *
 * 这与 InitFileAccess() 是分开的，因为临时文件清理可能会导致 pgstat 报告。由于在 before_shmem_exit() 期间关闭 pgstat，我们的报告必须在此之前进行。低级文件访问应保持更长时间可用，因此临时文件处理的初始化/关闭是分开的。
 */
void InitTemporaryFileAccess(void)
{
	Assert(SizeVfdCache != 0);	/* InitFileAccess() 需要运行过 */
	Assert(!temporary_files_allowed);	/* 只调用我一次 */

	/*
	 * 注册 before-shmem-exit 钩子，以确保在我们仍然可以报告统计信息时丢弃临时文件。
	 */
	before_shmem_exit(fc_BeforeShmemExit_Files, 0);

#ifdef USE_ASSERT_CHECKING
	temporary_files_allowed = true;
#endif
}

/*
 * count_usable_fds --- 计算系统允许我们打开的文件描述符数量，
 *	并估计已经打开的数量。
 *
 * 如果 usable_fds 达到 max_to_probe 就停止计数。注意：较小的 max_to_probe 值可能会导致对 already_open 的低估；我们必须填补使用的文件描述符集合中的任何“空缺”，才能使 already_open 的计算得出正确结果。在实践中，几个几十的 max_to_probe 应该足以确保良好的结果。
 *
 * 我们假定 stderr (FD 2) 是可用于复制的。虽然调用脚本理论上可以关闭它，但这样做会是一个非常糟糕的主意，因为那样可能会导致丢失来自 libc 的错误信息。
 */
static void fc_count_usable_fds(int fc_max_to_probe, int *fc_usable_fds, int *fc_already_open)
{
	int		   *fc_fd;
	int			fc_size;
	int			fc_used = 0;
	int			fc_highestfd = 0;
	int			fc_j;

#ifdef HAVE_GETRLIMIT
	struct rlimit fc_rlim;
	int			fc_getrlimit_status;
#endif

	fc_size = 1024;
	fc_fd = (int *) palloc(fc_size * sizeof(int));

#ifdef HAVE_GETRLIMIT
#ifdef RLIMIT_NOFILE			/* 大多数平台使用 RLIMIT_NOFILE */
	fc_getrlimit_status = getrlimit(RLIMIT_NOFILE, &fc_rlim);
#else							/* 但 BSD 不使用… */
	fc_getrlimit_status = getrlimit(RLIMIT_OFILE, &fc_rlim);
#endif							/* RLIMIT_NOFILE */
	if (fc_getrlimit_status != 0)
		ereport(WARNING, (errmsg("getrlimit failed: %m")));
#endif							/* HAVE_GETRLIMIT */

	/* 复制直到失败或达到探测限制 */
	for (;;)
	{
		int			fc_thisfd;

#ifdef HAVE_GETRLIMIT

		/*
		 * 不要超出 RLIMIT_NOFILE；在某些平台上，这会导致烦人的内核日志
		 */
		if (fc_getrlimit_status == 0 && fc_highestfd >= fc_rlim.rlim_cur - 1)
			break;
#endif

		fc_thisfd = dup(2);
		if (fc_thisfd < 0)
		{
			/* 期待 EMFILE 或 ENFILE，否则就是有问题 */
			if (errno != EMFILE && errno != ENFILE)
				elog(WARNING, "duplicating stderr file descriptor failed after %d successes: %m", fc_used);
			break;
		}

		if (fc_used >= fc_size)
		{
			fc_size *= 2;
			fc_fd = (int *) repalloc(fc_fd, fc_size * sizeof(int));
		}
		fc_fd[fc_used++] = fc_thisfd;

		if (fc_highestfd < fc_thisfd)
			fc_highestfd = fc_thisfd;

		if (fc_used >= fc_max_to_probe)
			break;
	}

	/* 释放我们打开的文件 */
	for (fc_j = 0; fc_j < fc_used; fc_j++)
		close(fc_fd[fc_j]);

	pfree(fc_fd);

	/*
	 * 返回结果。usable_fds仅为成功复制的数量。我们假定系统限制是 highestfd+1（记住 0 是合法的 FD 编号），因此 already_open 是 highestfd+1 - usable_fds。
	 */
	*fc_usable_fds = fc_used;
	*fc_already_open = fc_highestfd + 1 - fc_used;
}

/*
 * set_max_safe_fds
 *		确定 fd.c 允许使用的文件描述符数量
 */
void set_max_safe_fds(void)
{
	int			fc_usable_fds;
	int			fc_already_open;

	/*----------
	 * 我们想将 max_safe_fds 设置为
	 *			MIN(usable_fds, max_files_per_process - already_open)
	 * 减去在未咨询 fd.c 的情况下打开文件的冗余因子。这确保我们不会超过 max_files_per_process 
	 * 或实验确定的 EMFILE 限制。
	 *----------
	 */
	fc_count_usable_fds(max_files_per_process,
					 &fc_usable_fds, &fc_already_open);

	max_safe_fds = Min(fc_usable_fds, max_files_per_process - fc_already_open);

	/*
	 * 去掉保留给 system() 等的文件描述符。
	 */
	max_safe_fds -= NUM_RESERVED_FDS;

	/*
	 * 确保我们仍然有足够的可用文件描述符。
	 */
	if (max_safe_fds < FD_MINFREE)
		ereport(FATAL,
				(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
				 errmsg("insufficient file descriptors available to start server process"),
				 errdetail("System allows %d, we need at least %d.",
						   max_safe_fds + NUM_RESERVED_FDS,
						   FD_MINFREE + NUM_RESERVED_FDS)));

	elog(DEBUG2, "max_safe_fds = %d, usable_fds = %d, already_open = %d",
		 max_safe_fds, fc_usable_fds, fc_already_open);
}

/*
 * 使用 BasicOpenFilePerm() 打开文件，并为 fileMode 参数传递默认文件模式。
 */
int BasicOpenFile(const char *fc_fileName, int fc_fileFlags)
{
	return BasicOpenFilePerm(fc_fileName, fc_fileFlags, pg_file_create_mode);
}

/*
 * BasicOpenFilePerm --- 与 open(2) 相同，但在需要时可以释放其他文件描述符
 *
 * 该函数导出供需要普通内核文件描述符的地方使用，但需要防止文件描述符用尽。一旦成功返回文件描述符，调用方有责任确保在 ereport() 时不会泄漏！大多数用户*不应该*直接调用此例程，而应使用 VFD 抽象层，该层提供了防止描述符泄漏以及管理需打开超过短时间的文件的机制。
 *
 * 理想情况下，这应该是后端中*唯一*直接调用的 open()。在实践中，postmaster 会直接调用 open()，并且在后端启动早期会执行一些直接的 open() 调用。这是可以的，因为在那时该模块根本没有任何打开的文件可以关闭。
 */
int BasicOpenFilePerm(const char *fc_fileName, int fc_fileFlags, mode_t fc_fileMode)
{
	int			fc_fd;

tryAgain:
#ifdef PG_O_DIRECT_USE_F_NOCACHE

	
/*
	 * 我们为 O_DIRECT 定义的值在用 F_NOCACHE 模拟时最好不与任何标准标志冲突。
	 */
	StaticAssertStmt((PG_O_DIRECT &
					  (O_APPEND |
					   O_CREAT |
					   O_EXCL |
					   O_RDWR |
					   O_RDONLY |
					   O_SYNC |
					   O_TRUNC |
					   O_WRONLY)) == 0,
					 "PG_O_DIRECT value collides with standard flag");
#if defined(O_CLOEXEC)
	StaticAssertStmt((PG_O_DIRECT & O_CLOEXEC) == 0,
					 "PG_O_DIRECT value collides with O_CLOEXEC");
#endif
#if defined(O_DSYNC)
	StaticAssertStmt((PG_O_DIRECT & O_DSYNC) == 0,
					 "PG_O_DIRECT value collides with O_DSYNC");
#endif

	fc_fd = open(fc_fileName, fc_fileFlags & ~PG_O_DIRECT, fc_fileMode);
#else
	fc_fd = open(fc_fileName, fc_fileFlags, fc_fileMode);
#endif

	if (fc_fd >= 0)
	{
#ifdef PG_O_DIRECT_USE_F_NOCACHE
		if (fc_fileFlags & PG_O_DIRECT)
		{
			if (fcntl(fc_fd, F_NOCACHE, 1) < 0)
			{
				int			fc_save_errno = errno;

				close(fc_fd);
				errno = fc_save_errno;
				return -1;
			}
		}
#endif

		return fc_fd;				/* 成功！ */
	}

	if (errno == EMFILE || errno == ENFILE)
	{
		int			fc_save_errno = errno;

		ereport(LOG,
				(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
				 errmsg("out of file descriptors: %m; release and retry")));
		errno = 0;
		if (fc_ReleaseLruFile())
			goto tryAgain;
		errno = fc_save_errno;
	}

	return -1;					/* 失败 */
}

/*
 * AcquireExternalFD - 尝试保留一个外部文件描述符
 *
 * 这应该由需要在较长时间内保持文件描述符打开但不能使用本模块提供的其他功能的调用者使用。
 *
 * 这种方法与底层的 ReserveExternalFD 函数的区别在于，如果“外部”文件描述符已经保留得“太多”，它将通过设置 errno 并返回 false 报告失败。 这应该在任何无法预测和较少的保留文件描述符总数的代码中使用。
 */
bool AcquireExternalFD(void)
{
	/*
	 * 我们不希望为“外部”文件描述符消耗超过 max_safe_fds / 3 的文件描述符。
	 */
	if (numExternalFDs < max_safe_fds / 3)
	{
		ReserveExternalFD();
		return true;
	}
	errno = EMFILE;
	return false;
}

/*
 * ReserveExternalFD - 报告外部文件描述符的使用
 *
 * 这应该由需要在较长时间内保持文件描述符打开但不能使用本模块提供的其他功能的调用者使用。 这只是跟踪文件描述符的使用情况，并在需要时关闭虚拟文件描述符，以确保我们保持 NUM_RESERVED_FDS 个文件描述符可用。
 *
 * 仅在保留文件描述符失败会造成致命后果的代码中直接调用此函数，例如，WAL 写入的代码会这样做，因为替代情况是会话失败。此外，在可能每个进程消耗多个文件描述符的代码中这样做是非常不明智的。
 *
 * 注意：只要每个人都保持良好，以确保 NUM_RESERVED_FDS 文件描述符保持可用，无论在实际打开文件描述符之前还是之后调用此函数都没有太大关系；但提前这样做可以降低如果不是每个人都保持良好的话导致 EMFILE 失败的风险。 在任何情况下，保持外部文件描述符计数与现实同步完全是调用者的责任。
 */
void ReserveExternalFD(void)
{
	/*
	 * 如果需要释放虚拟文件描述符，以保持安全。因为我们在增加 numExternalFDs 之前执行此操作，所以最终状态将如预期，即 nfile + numAllocatedDescs + numExternalFDs <= max_safe_fds。
	 */
	fc_ReleaseLruFiles();

	numExternalFDs++;
}

/*
 * ReleaseExternalFD - 报告释放外部文件描述符
 *
 * 这保证不会改变 errno，因此可以在失败路径中使用。
 */
void ReleaseExternalFD(void)
{
	Assert(numExternalFDs > 0);
	numExternalFDs--;
}


#if defined(FDDEBUG)

static void fc__dump_lru(void)
{
	int			fc_mru = VfdCache[0].lruLessRecently;
	Vfd		   *fc_vfdP = &VfdCache[fc_mru];
	char		fc_buf[2048];

	snprintf(fc_buf, sizeof(fc_buf), "LRU: MOST %d ", fc_mru);
	while (fc_mru != 0)
	{
		fc_mru = fc_vfdP->lruLessRecently;
		fc_vfdP = &VfdCache[fc_mru];
		snprintf(fc_buf + strlen(fc_buf), sizeof(fc_buf) - strlen(fc_buf), "%d ", fc_mru);
	}
	snprintf(fc_buf + strlen(fc_buf), sizeof(fc_buf) - strlen(fc_buf), "LEAST");
	elog(LOG, "%s", fc_buf);
}
#endif							/* FDDEBUG */

static void fc_Delete(File fc_file)
{
	Vfd		   *fc_vfdP;

	Assert(fc_file != 0);

	DO_DB(elog(LOG, "Delete %d (%s)",
			   fc_file, VfdCache[fc_file].fileName));
	DO_DB(fc__dump_lru());

	fc_vfdP = &VfdCache[fc_file];

	VfdCache[fc_vfdP->lruLessRecently].lruMoreRecently = fc_vfdP->lruMoreRecently;
	VfdCache[fc_vfdP->lruMoreRecently].lruLessRecently = fc_vfdP->lruLessRecently;

	DO_DB(fc__dump_lru());
}

static void fc_LruDelete(File fc_file)
{
	Vfd		   *fc_vfdP;

	Assert(fc_file != 0);

	DO_DB(elog(LOG, "LruDelete %d (%s)",
			   fc_file, VfdCache[fc_file].fileName));

	fc_vfdP = &VfdCache[fc_file];

	/*
	 * 关闭文件。我们并不期望这会失败；如果确实失败，泄露文件描述符比破坏我们的内部状态要好。
	 */
	if (close(fc_vfdP->fd) != 0)
		elog(fc_vfdP->fdstate & FD_TEMP_FILE_LIMIT ? LOG : data_sync_elevel(LOG),
			 "could not close file \"%s\": %m", fc_vfdP->fileName);
	fc_vfdP->fd = VFD_CLOSED;
	--nfile;

	/* 从LRU环中删除vfd记录 */
	fc_Delete(fc_file);
}

static void fc_Insert(File fc_file)
{
	Vfd		   *fc_vfdP;

	Assert(fc_file != 0);

	DO_DB(elog(LOG, "Insert %d (%s)",
			   fc_file, VfdCache[fc_file].fileName));
	DO_DB(fc__dump_lru());

	fc_vfdP = &VfdCache[fc_file];

	fc_vfdP->lruMoreRecently = 0;
	fc_vfdP->lruLessRecently = VfdCache[0].lruLessRecently;
	VfdCache[0].lruLessRecently = fc_file;
	VfdCache[fc_vfdP->lruLessRecently].lruMoreRecently = fc_file;

	DO_DB(fc__dump_lru());
}

/* 成功返回0，重新打开失败时返回-1（errno被设置） */
static int fc_LruInsert(File fc_file)
{
	Vfd		   *fc_vfdP;

	Assert(fc_file != 0);

	DO_DB(elog(LOG, "LruInsert %d (%s)",
			   fc_file, VfdCache[fc_file].fileName));

	fc_vfdP = &VfdCache[fc_file];

	if (FileIsNotOpen(fc_file))
	{
		/* 关闭多余的内核文件描述符。 */
		fc_ReleaseLruFiles();

		/*
		 * 打开仍可能因缺少文件描述符而失败，例如由于整体系统文件表已满。因此，要做好必要时释放另一个文件描述符的准备...
		 */
		fc_vfdP->fd = BasicOpenFilePerm(fc_vfdP->fileName, fc_vfdP->fileFlags,
									 fc_vfdP->fileMode);
		if (fc_vfdP->fd < 0)
		{
			DO_DB(elog(LOG, "re-open failed: %m"));
			return -1;
		}
		else
		{
			++nfile;
		}
	}

	/*
	 * 将其放在LRU环的头部
	 */

	fc_Insert(fc_file);

	return 0;
}

/*
 * 通过关闭使用最少的VFD来释放一个内核文件描述符。
 */
static bool fc_ReleaseLruFile(void)
{
	DO_DB(elog(LOG, "ReleaseLruFile. Opened %d", nfile));

	if (nfile > 0)
	{
		/*
		 * 有打开的文件，因此环中至少应该有一个正在使用的vfd。
		 */
		Assert(VfdCache[0].lruMoreRecently != 0);
		fc_LruDelete(VfdCache[0].lruMoreRecently);
		return true;			/* 释放了一个文件 */
	}
	return false;				/* 没有可释放的文件 */
}

/*
 * 根据需要释放内核文件描述符，以使其低于max_safe_fds限制。
 * 在调用此方法后，可以尝试打开另一个文件。
 */
static void fc_ReleaseLruFiles(void)
{
	while (nfile + numAllocatedDescs + numExternalFDs >= max_safe_fds)
	{
		if (!fc_ReleaseLruFile())
			break;
	}
}

static File fc_AllocateVfd(void)
{
	Index		fc_i;
	File		fc_file;

	DO_DB(elog(LOG, "AllocateVfd. Size %zu", SizeVfdCache));

	Assert(SizeVfdCache > 0);	/* 未调用InitFileAccess？ */

	if (VfdCache[0].nextFree == 0)
	{
		/*
		 * 自由列表为空，因此是时候增加数组的大小了。
		 * 我们选择每次将其翻倍。然而，开始时真的没必要太小。
		 */
		Size		fc_newCacheSize = SizeVfdCache * 2;
		Vfd		   *fc_newVfdCache;

		if (fc_newCacheSize < 32)
			fc_newCacheSize = 32;

		/*
		 * 小心不要在realloc失败时损坏VfdCache指针。
		 */
		fc_newVfdCache = (Vfd *) realloc(VfdCache, sizeof(Vfd) * fc_newCacheSize);
		if (fc_newVfdCache == NULL)
			ereport(ERROR,
					(errcode(ERRCODE_OUT_OF_MEMORY),
					 errmsg("out of memory")));
		VfdCache = fc_newVfdCache;

		/*
		 * 初始化新条目并将它们链接到空闲列表中。
		 */
		for (fc_i = SizeVfdCache; fc_i < fc_newCacheSize; fc_i++)
		{
			MemSet((char *) &(VfdCache[fc_i]), 0, sizeof(Vfd));
			VfdCache[fc_i].nextFree = fc_i + 1;
			VfdCache[fc_i].fd = VFD_CLOSED;
		}
		VfdCache[fc_newCacheSize - 1].nextFree = 0;
		VfdCache[0].nextFree = SizeVfdCache;

		/*
		 * 记录新大小
		 */
		SizeVfdCache = fc_newCacheSize;
	}

	fc_file = VfdCache[0].nextFree;

	VfdCache[0].nextFree = VfdCache[fc_file].nextFree;

	return fc_file;
}

static void fc_FreeVfd(File fc_file)
{
	Vfd		   *fc_vfdP = &VfdCache[fc_file];

	DO_DB(elog(LOG, "FreeVfd: %d (%s)",
			   fc_file, fc_vfdP->fileName ? fc_vfdP->fileName : ""));

	if (fc_vfdP->fileName != NULL)
	{
		free(fc_vfdP->fileName);
		fc_vfdP->fileName = NULL;
	}
	fc_vfdP->fdstate = 0x0;

	fc_vfdP->nextFree = VfdCache[0].nextFree;
	VfdCache[0].nextFree = fc_file;
}

/* 成功返回0，重新打开失败时返回-1（errno被设置） */
static int fc_FileAccess(File fc_file)
{
	int			fc_returnValue;

	DO_DB(elog(LOG, "FileAccess %d (%s)",
			   fc_file, VfdCache[fc_file].fileName));

	/*
	 * 文件是打开的吗？如果没有，打开它并将其放在LRU环的头部
	 * （可能关闭使用最少的文件以获取一个文件描述符）。
	 */

	if (FileIsNotOpen(fc_file))
	{
		fc_returnValue = fc_LruInsert(fc_file);
		if (fc_returnValue != 0)
			return fc_returnValue;
	}
	else if (VfdCache[0].lruLessRecently != fc_file)
	{
		/*
		 * 我们现在知道文件是打开的，并且不是最后一个被访问的文件，
		 * 因此我们需要将其移动到LRU环的头部。
		 */

		fc_Delete(fc_file);
		fc_Insert(fc_file);
	}

	return 0;
}

/*
 * 每当删除临时文件时调用以报告其大小。
 */
static void fc_ReportTemporaryFileUsage(const char *fc_path, off_t fc_size)
{
	pgstat_report_tempfile(fc_size);

	if (log_temp_files >= 0)
	{
		if ((fc_size / 1024) >= log_temp_files)
			ereport(LOG,
					(errmsg("temporary file: path \"%s\", size %lu",
							fc_path, (unsigned long) fc_size)));
	}
}

/*
 * 调用以注册临时文件以进行自动关闭。
 * 必须在打开文件之前调用ResourceOwnerEnlargeFiles(CurrentResourceOwner)。
 */
static void fc_RegisterTemporaryFile(File fc_file)
{
	ResourceOwnerRememberFile(CurrentResourceOwner, fc_file);
	VfdCache[fc_file].resowner = CurrentResourceOwner;

	/* 在事务结束时关闭的备份机制。 */
	VfdCache[fc_file].fdstate |= FD_CLOSE_AT_EOXACT;
	have_xact_temporary_files = true;
}

/*
 * 当我们收到某个关系的共享失效消息时调用。
 */
#ifdef NOT_USED
void FileInvalidate(File fc_file)
{
	Assert(FileIsValid(fc_file));
	if (!FileIsNotOpen(fc_file))
		fc_LruDelete(fc_file);
}
#endif

/*
 * 使用PathNameOpenFilePerm()打开文件，并为fileMode参数传递默认文件模式。
 */
File PathNameOpenFile(const char *fc_fileName, int fc_fileFlags)
{
	return PathNameOpenFilePerm(fc_fileName, fc_fileFlags, pg_file_create_mode);
}

/*
 * 在任意目录中打开文件
 *
 * 注意：如果传递的路径名是相对的（通常是这样），
 * 将相对于进程的工作目录进行解释
 * （在此代码运行时通常应始终为$PGDATA）。
 */
File PathNameOpenFilePerm(const char *fc_fileName, int fc_fileFlags, mode_t fc_fileMode)
{
	char	   *fc_fnamecopy;
	File		fc_file;
	Vfd		   *fc_vfdP;

	DO_DB(elog(LOG, "PathNameOpenFilePerm: %s %x %o",
			   fc_fileName, fc_fileFlags, fc_fileMode));

	/*
	 * 我们需要文件名的malloc'd副本；如果没有空间，则干净地失败。
	 */
	fc_fnamecopy = strdup(fc_fileName);
	if (fc_fnamecopy == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of memory")));

	fc_file = fc_AllocateVfd();
	fc_vfdP = &VfdCache[fc_file];

	/* 关闭多余的内核文件描述符。 */
	fc_ReleaseLruFiles();

	fc_vfdP->fd = BasicOpenFilePerm(fc_fileName, fc_fileFlags, fc_fileMode);

	if (fc_vfdP->fd < 0)
	{
		int			fc_save_errno = errno;

		fc_FreeVfd(fc_file);
		free(fc_fnamecopy);
		errno = fc_save_errno;
		return -1;
	}
	++nfile;
	DO_DB(elog(LOG, "PathNameOpenFile: success %d",
			   fc_vfdP->fd));

	fc_vfdP->fileName = fc_fnamecopy;
	/* 保存的标志已调整为适合重新打开文件 */
	fc_vfdP->fileFlags = fc_fileFlags & ~(O_CREAT | O_TRUNC | O_EXCL);
	fc_vfdP->fileMode = fc_fileMode;
	fc_vfdP->fileSize = 0;
	fc_vfdP->fdstate = 0x0;
	fc_vfdP->resowner = NULL;

	fc_Insert(fc_file);

	return fc_file;
}

/*
 * 创建'directory'目录。如有必要，创建'basedir'，该目录必须
 * 是其上面的目录。此操作旨在根据需要创建顶级临时目录，然后在其下创建一个目录。
 * 如果目录已存在，则不执行任何操作。
 *
 * 在顶级临时目录中创建的目录应以PG_TEMP_FILE_PREFIX开头，以便可以将其识别为临时目录，并在启动时由RemovePgTempFiles()删除。
 * 进一步的子目录则不需要任何特定的前缀。
 */
void PathNameCreateTemporaryDir(const char *fc_basedir, const char *fc_directory)
{
	if (MakePGDirectory(fc_directory) < 0)
	{
		if (errno == EEXIST)
			return;

		/*
		 * 失败。首先尝试创建basedir，以防其缺失。容忍
		 * EEXIST以避免与另一个遵循相同算法的进程之间的竞态。
		 */
		if (MakePGDirectory(fc_basedir) < 0 && errno != EEXIST)
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("cannot create temporary directory \"%s\": %m",
							fc_basedir)));

		/* 再试一次。 */
		if (MakePGDirectory(fc_directory) < 0 && errno != EEXIST)
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("cannot create temporary subdirectory \"%s\": %m",
							fc_directory)));
	}
}

/*
 * 如果目录存在，则删除该目录及其中的所有内容。
 */
void PathNameDeleteTemporaryDir(const char *fc_dirname)
{
	struct stat fc_statbuf;

	/* 安静地忽略缺少的目录。 */
	if (stat(fc_dirname, &fc_statbuf) != 0 && errno == ENOENT)
		return;

	/*
	 * 当前，walkdir没有提供一种方式让我们传入的函数
	 * 保持状态。也许应该有这样的功能，这样我们可以告知调用者
	 * 此操作是成功还是失败。由于此操作用于清理路径，
	 * 实际上我们不会表现出不同的行为：我们只会记录失败。
	 */
	fc_walkdir(fc_dirname, fc_unlink_if_exists_fname, false, LOG);
}

/*
 * 打开一个临时文件，当我们关闭它时它将消失。
 *
 * 这个例程处理生成一个合适的临时文件名。
 * 也不需要传入fileFlags或fileMode，因为只有
 * 一种设置对于临时文件是合理的。
 *
 * 除非interXact为真，否则该文件会被CurrentResourceOwner记住
 * 以确保它在不再需要时关闭和删除，通常是在
 * 事务结束时。在大多数情况下，你不希望临时文件
 * 超过创建它们的事务的生命周期，因此这应该为假——但是
 * 如果你需要“某种程度上的”临时存储，这可能会很有用。
 * 在任何情况下，当文件显式关闭时，文件将被移除。
 */
File OpenTemporaryFile(bool fc_interXact)
{
	File		fc_file = 0;

	Assert(temporary_files_allowed);	/* 检查临时文件访问是否正常 */

	/*
	 * 在打开文件之前确保当前资源所有者有足够的空间来
	 * 存放该文件，如果我们将在下面注册它的话。
	 */
	if (!fc_interXact)
		ResourceOwnerEnlargeFiles(CurrentResourceOwner);

	/*
	 * 如果我们收到了一些临时表空间，尝试使用下一个
	 * 表空间。如果找不到给定的表空间，我们会默默地
	 * 回退到数据库的默认表空间。
	 *
	 * 但是：如果临时文件计划超出当前事务的生命周期，
	 * 强制将其放入数据库的默认表空间，以免它对可能的
	 * 表空间删除尝试构成威胁。
	 */
	if (numTempTableSpaces > 0 && !fc_interXact)
	{
		Oid			fc_tblspcOid = GetNextTempTableSpace();

		if (OidIsValid(fc_tblspcOid))
			fc_file = fc_OpenTemporaryFileInTablespace(fc_tblspcOid, false);
	}

	/*
	 * 如果没有，或者如果表空间无效，则在数据库的默认
	 * 表空间中创建。MyDatabaseTableSpace通常应该在我们到达
	 * 此处之前设置，但以防没有，回退到pg_default表空间。
	 */
	if (fc_file <= 0)
		fc_file = fc_OpenTemporaryFileInTablespace(MyDatabaseTableSpace ?
											 MyDatabaseTableSpace :
											 DEFAULTTABLESPACE_OID,
											 true);

	/* 标记为在关闭时删除和临时文件大小限制 */
	VfdCache[fc_file].fdstate |= FD_DELETE_AT_CLOSE | FD_TEMP_FILE_LIMIT;

	/* 与当前资源所有者注册 */
	if (!fc_interXact)
		fc_RegisterTemporaryFile(fc_file);

	return fc_file;
}

/*
 * 返回给定表空间中临时目录的路径。
 */
void TempTablespacePath(char *fc_path, Oid fc_tablespace)
{
	/*
	 * 识别该表空间的临时文件目录。
	 *
	 * 如果有人试图指定pg_global，则使用pg_default代替。
	 */
	if (fc_tablespace == InvalidOid ||
		fc_tablespace == DEFAULTTABLESPACE_OID ||
		fc_tablespace == GLOBALTABLESPACE_OID)
		snprintf(fc_path, MAXPGPATH, "base/%s", PG_TEMP_FILES_DIR);
	else
	{
		/* 所有其他表空间通过符号链接访问 */
		snprintf(fc_path, MAXPGPATH, "pg_tblspc/%u/%s/%s",
				 fc_tablespace, TABLESPACE_VERSION_DIRECTORY,
				 PG_TEMP_FILES_DIR);
	}
}

/*
 * 在特定表空间中打开一个临时文件。
 * 这是OpenTemporaryFile的子例程，详细信息请参阅。
 */
static File fc_OpenTemporaryFileInTablespace(Oid fc_tblspcOid, bool fc_rejectError)
{
	char		fc_tempdirpath[MAXPGPATH];
	char		fc_tempfilepath[MAXPGPATH];
	File		fc_file;

	TempTablespacePath(fc_tempdirpath, fc_tblspcOid);

	/*
	 * 生成一个临时文件名，该文件名在当前数据库实例中应该是唯一的。
	 */
	snprintf(fc_tempfilepath, sizeof(fc_tempfilepath), "%s/%s%d.%ld",
			 fc_tempdirpath, PG_TEMP_FILE_PREFIX, MyProcPid, tempFileCounter++);

	/*
	 * 打开文件。注意：我们不使用O_EXCL，以防有一个孤立的
	 * 临时文件可以被重用。
	 */
	fc_file = PathNameOpenFile(fc_tempfilepath,
							O_RDWR | O_CREAT | O_TRUNC | PG_BINARY);
	if (fc_file <= 0)
	{
		/*
		 * 如果还没有人创建表空间的临时文件目录，我们可能需要
		 * 创建它。
		 *
		 * 不要检查MakePGDirectory的错误；如果其他人刚刚做了同样的事情，它可能会失败。
		 * 如果它不起作用，我们将在第二次创建尝试时出错。
		 */
		(void) MakePGDirectory(fc_tempdirpath);

		fc_file = PathNameOpenFile(fc_tempfilepath,
								O_RDWR | O_CREAT | O_TRUNC | PG_BINARY);
		if (fc_file <= 0 && fc_rejectError)
			elog(ERROR, "could not create temporary file \"%s\": %m",
				 fc_tempfilepath);
	}

	return fc_file;
}


/*
 * 创建一个新文件。包含该文件的目录必须已经存在。
 * 以这种方式创建的文件受到temp_file_limit的限制，并且在交易结束时自动关闭，
 * 但在关闭时不会自动删除，因为它们旨在在合作后端之间共享。
 *
 * 如果文件在顶级临时目录中，则其名称应以PG_TEMP_FILE_PREFIX开头，以便可以
 * 识别为临时文件，并在启动时由RemovePgTempFiles()删除。或者，它可以
 * 在使用PathNameCreateTemporaryDir()创建的目录中，在这种情况下不需要前缀。
 */
File PathNameCreateTemporaryFile(const char *fc_path, bool fc_error_on_failure)
{
	File		fc_file;

	Assert(temporary_files_allowed);	/* 检查临时文件访问是否正常 */

	ResourceOwnerEnlargeFiles(CurrentResourceOwner);

	/*
	 * 打开文件。注意：我们不使用O_EXCL，以防有一个孤立的
	 * 临时文件可以被重用。
	 */
	fc_file = PathNameOpenFile(fc_path, O_RDWR | O_CREAT | O_TRUNC | PG_BINARY);
	if (fc_file <= 0)
	{
		if (fc_error_on_failure)
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not create temporary file \"%s\": %m",
							fc_path)));
		else
			return fc_file;
	}

	/* 将其标记为temp_file_limit会计。 */
	VfdCache[fc_file].fdstate |= FD_TEMP_FILE_LIMIT;

	/* 注册以自动关闭。 */
	fc_RegisterTemporaryFile(fc_file);

	return fc_file;
}

/*
 * 打开一个由PathNameCreateTemporaryFile创建的文件，可能来自
 * 另一个后端。以这种方式打开的文件不计入调用者的
 * temp_file_limit，在事务结束时自动关闭，但在关闭时不会删除。
 */
File PathNameOpenTemporaryFile(const char *fc_path, int fc_mode)
{
	File		fc_file;

	Assert(temporary_files_allowed);	/* 检查临时文件访问是否正常 */

	ResourceOwnerEnlargeFiles(CurrentResourceOwner);

	fc_file = PathNameOpenFile(fc_path, fc_mode | PG_BINARY);

	/* 如果没有这样的文件，则我们不会引发错误。 */
	if (fc_file <= 0 && errno != ENOENT)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not open temporary file \"%s\": %m",
						fc_path)));

	if (fc_file > 0)
	{
		/* 注册以自动关闭。 */
		fc_RegisterTemporaryFile(fc_file);
	}

	return fc_file;
}

/*
 * 通过路径名删除文件。如果文件存在则返回true，如果不存在则返回false。
 */
bool PathNameDeleteTemporaryFile(const char *fc_path, bool fc_error_on_failure)
{
	struct stat fc_filestats;
	int			fc_stat_errno;

	/* 获取pgstat报告的最终大小。 */
	if (stat(fc_path, &fc_filestats) != 0)
		fc_stat_errno = errno;
	else
		fc_stat_errno = 0;

	/*
	 * 与FileClose的自动文件删除代码不同，我们容忍
	 * 不存在，以支持BufFileDeleteFileSet，该函数不知道有多少
	 * 段要删除，直到它耗尽。
	 */
	if (fc_stat_errno == ENOENT)
		return false;

	if (unlink(fc_path) < 0)
	{
		if (errno != ENOENT)
			ereport(fc_error_on_failure ? ERROR : LOG,
					(errcode_for_file_access(),
					 errmsg("could not unlink temporary file \"%s\": %m",
							fc_path)));
		return false;
	}

	if (fc_stat_errno == 0)
		fc_ReportTemporaryFileUsage(fc_path, fc_filestats.st_size);
	else
	{
		errno = fc_stat_errno;
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not stat file \"%s\": %m", fc_path)));
	}

	return true;
}

/*
 * 完成后关闭文件
 */
void FileClose(File fc_file)
{
	Vfd		   *fc_vfdP;

	Assert(FileIsValid(fc_file));

	DO_DB(elog(LOG, "FileClose: %d (%s)",
			   fc_file, VfdCache[fc_file].fileName));

	fc_vfdP = &VfdCache[fc_file];

	if (!FileIsNotOpen(fc_file))
	{
		
/* 关闭文件 */
		if (close(fc_vfdP->fd) != 0)
		{
			/*
			 * 如果无法关闭非临时文件，我们可能需要触发紧急情况；
			 * 参见 LruDelete。
			 */
			elog(fc_vfdP->fdstate & FD_TEMP_FILE_LIMIT ? LOG : data_sync_elevel(LOG),
				 "could not close file \"%s\": %m", fc_vfdP->fileName);
		}

		--nfile;
		fc_vfdP->fd = VFD_CLOSED;

		/* 从 lru 链中移除文件 */
		fc_Delete(fc_file);
	}

	if (fc_vfdP->fdstate & FD_TEMP_FILE_LIMIT)
	{
		/* 从当前使用中减去它的大小（先做以防出错） */
		temporary_files_size -= fc_vfdP->fileSize;
		fc_vfdP->fileSize = 0;
	}

	/*
	 * 如果文件是临时的，则删除文件，并在需要时记录日志
	 */
	if (fc_vfdP->fdstate & FD_DELETE_AT_CLOSE)
	{
		struct stat fc_filestats;
		int			fc_stat_errno;

		/*
		 * 如果我们遇到错误，如在ereport/elog调用中可能发生的那样，
		 * 我们将在事务中止时立即返回这里。重置标志以确保我们不会进入
		 * 无限循环。这个代码安排确保最坏的结果是未能发出日志信息，
		 * 而不是未能尝试解除链接。
		 */
		fc_vfdP->fdstate &= ~FD_DELETE_AT_CLOSE;


		/* 首先尝试stat() */
		if (stat(fc_vfdP->fileName, &fc_filestats))
			fc_stat_errno = errno;
		else
			fc_stat_errno = 0;

		/* 无论如何都要进行解除链接 */
		if (unlink(fc_vfdP->fileName))
			ereport(LOG,
					(errcode_for_file_access(),
					 errmsg("could not delete file \"%s\": %m", fc_vfdP->fileName)));

		/* 最后报告stat结果 */
		if (fc_stat_errno == 0)
			fc_ReportTemporaryFileUsage(fc_vfdP->fileName, fc_filestats.st_size);
		else
		{
			errno = fc_stat_errno;
			ereport(LOG,
					(errcode_for_file_access(),
					 errmsg("could not stat file \"%s\": %m", fc_vfdP->fileName)));
		}
	}

	/* 从资源所有者中注销它 */
	if (fc_vfdP->resowner)
		ResourceOwnerForgetFile(fc_vfdP->resowner, fc_file);

	/*
	 * 将Vfd槽返回到空闲列表
	 */
	fc_FreeVfd(fc_file);
}

/*
 * FilePrefetch - 启动对给定范围文件的异步读取。
 *
 * 目前该函数的唯一实现是使用posix_fadvise，
 * 这是实现该功能的最简单标准化接口。
 * 我们可以在未来添加使用libaio的实现；但请注意，
 * 这个API对于libaio是不适合的，因为libaio希望提供一个缓冲区
 * 供读取使用。
 */
int FilePrefetch(File fc_file, off_t fc_offset, int fc_amount, uint32 fc_wait_event_info)
{
#if defined(USE_POSIX_FADVISE) && defined(POSIX_FADV_WILLNEED)
	int			fc_returnCode;

	Assert(FileIsValid(fc_file));

	DO_DB(elog(LOG, "FilePrefetch: %d (%s) " INT64_FORMAT " %d",
			   fc_file, VfdCache[fc_file].fileName,
			   (int64) fc_offset, fc_amount));

	fc_returnCode = fc_FileAccess(fc_file);
	if (fc_returnCode < 0)
		return fc_returnCode;

	pgstat_report_wait_start(fc_wait_event_info);
	fc_returnCode = posix_fadvise(VfdCache[fc_file].fd, fc_offset, fc_amount,
							   POSIX_FADV_WILLNEED);
	pgstat_report_wait_end();

	return fc_returnCode;
#else
	Assert(FileIsValid(fc_file));
	return 0;
#endif
}

void FileWriteback(File fc_file, off_t fc_offset, off_t fc_nbytes, uint32 fc_wait_event_info)
{
	int			fc_returnCode;

	Assert(FileIsValid(fc_file));

	DO_DB(elog(LOG, "FileWriteback: %d (%s) " INT64_FORMAT " " INT64_FORMAT,
			   fc_file, VfdCache[fc_file].fileName,
			   (int64) fc_offset, (int64) fc_nbytes));

	if (fc_nbytes <= 0)
		return;

	fc_returnCode = fc_FileAccess(fc_file);
	if (fc_returnCode < 0)
		return;

	pgstat_report_wait_start(fc_wait_event_info);
	pg_flush_data(VfdCache[fc_file].fd, fc_offset, fc_nbytes);
	pgstat_report_wait_end();
}

int FileRead(File fc_file, char *fc_buffer, int fc_amount, off_t fc_offset,
		 uint32 fc_wait_event_info)
{
	int			fc_returnCode;
	Vfd		   *fc_vfdP;

	Assert(FileIsValid(fc_file));

	DO_DB(elog(LOG, "FileRead: %d (%s) " INT64_FORMAT " %d %p",
			   fc_file, VfdCache[fc_file].fileName,
			   (int64) fc_offset,
			   fc_amount, fc_buffer));

	fc_returnCode = fc_FileAccess(fc_file);
	if (fc_returnCode < 0)
		return fc_returnCode;

	fc_vfdP = &VfdCache[fc_file];

retry:
	pgstat_report_wait_start(fc_wait_event_info);
	fc_returnCode = pg_pread(fc_vfdP->fd, fc_buffer, fc_amount, fc_offset);
	pgstat_report_wait_end();

	if (fc_returnCode < 0)
	{
		/*
		 * Windows可能会耗尽内核缓冲区并返回“系统资源不足”错误。
		 * 等待一下然后重试以解决此问题。
		 *
		 * 据传在某些Unix文件系统上EINTR也是可能的，
		 * 在这种情况下，需立即重试。
		 */
#ifdef WIN32
		DWORD		error = GetLastError();

		switch (error)
		{
			case ERROR_NO_SYSTEM_RESOURCES:
				pg_usleep(1000L);
				errno = EINTR;
				break;
			default:
				_dosmaperr(error);
				break;
		}
#endif
		/* 如果被中断，可以重试 */
		if (errno == EINTR)
			goto retry;
	}

	return fc_returnCode;
}

int FileWrite(File fc_file, char *fc_buffer, int fc_amount, off_t fc_offset,
		  uint32 fc_wait_event_info)
{
	int			fc_returnCode;
	Vfd		   *fc_vfdP;

	Assert(FileIsValid(fc_file));

	DO_DB(elog(LOG, "FileWrite: %d (%s) " INT64_FORMAT " %d %p",
			   fc_file, VfdCache[fc_file].fileName,
			   (int64) fc_offset,
			   fc_amount, fc_buffer));

	fc_returnCode = fc_FileAccess(fc_file);
	if (fc_returnCode < 0)
		return fc_returnCode;

	fc_vfdP = &VfdCache[fc_file];

	/*
	 * 如果强制temp_file_limit并且这是一个临时文件，
	 * 检查写入是否会超出temp_file_limit，如果是，则抛出错误。
	 * 注意：在这里抛出错误实际上是一种模块化违反；
	 * 我们应该设置errno并返回-1。然而，如果这样做，就没有办法报告
	 * 适当的错误信息。所有当前调用者无论如何都会立即抛出错误，
	 * 所以目前这样做是安全的。
	 */
	if (temp_file_limit >= 0 && (fc_vfdP->fdstate & FD_TEMP_FILE_LIMIT))
	{
		off_t		fc_past_write = fc_offset + fc_amount;

		if (fc_past_write > fc_vfdP->fileSize)
		{
			uint64		fc_newTotal = temporary_files_size;

			fc_newTotal += fc_past_write - fc_vfdP->fileSize;
			if (fc_newTotal > (uint64) temp_file_limit * (uint64) 1024)
				ereport(ERROR,
						(errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
						 errmsg("temporary file size exceeds temp_file_limit (%dkB)",
								temp_file_limit)));
		}
	}

retry:
	errno = 0;
	pgstat_report_wait_start(fc_wait_event_info);
	fc_returnCode = pg_pwrite(VfdCache[fc_file].fd, fc_buffer, fc_amount, fc_offset);
	pgstat_report_wait_end();

	/* 如果写入未设置 errno，假设问题是没有磁盘空间 */
	if (fc_returnCode != fc_amount && errno == 0)
		errno = ENOSPC;

	if (fc_returnCode >= 0)
	{
		/*
		 * 如果这是一个临时文件，维护fileSize和temporary_files_size。
		 */
		if (fc_vfdP->fdstate & FD_TEMP_FILE_LIMIT)
		{
			off_t		fc_past_write = fc_offset + fc_amount;

			if (fc_past_write > fc_vfdP->fileSize)
			{
				temporary_files_size += fc_past_write - fc_vfdP->fileSize;
				fc_vfdP->fileSize = fc_past_write;
			}
		}
	}
	else
	{
		/*
		 * 请参见FileRead()中的评论
		 */
#ifdef WIN32
		DWORD		error = GetLastError();

		switch (error)
		{
			case ERROR_NO_SYSTEM_RESOURCES:
				pg_usleep(1000L);
				errno = EINTR;
				break;
			default:
				_dosmaperr(error);
				break;
		}
#endif
		/* 如果被中断，可以重试 */
		if (errno == EINTR)
			goto retry;
	}

	return fc_returnCode;
}

int FileSync(File fc_file, uint32 fc_wait_event_info)
{
	int			fc_returnCode;

	Assert(FileIsValid(fc_file));

	DO_DB(elog(LOG, "FileSync: %d (%s)",
			   fc_file, VfdCache[fc_file].fileName));

	fc_returnCode = fc_FileAccess(fc_file);
	if (fc_returnCode < 0)
		return fc_returnCode;

	pgstat_report_wait_start(fc_wait_event_info);
	fc_returnCode = pg_fsync(VfdCache[fc_file].fd);
	pgstat_report_wait_end();

	return fc_returnCode;
}

off_t
FileSize(File fc_file)
{
	Assert(FileIsValid(fc_file));

	DO_DB(elog(LOG, "FileSize %d (%s)",
			   fc_file, VfdCache[fc_file].fileName));

	if (FileIsNotOpen(fc_file))
	{
		if (fc_FileAccess(fc_file) < 0)
			return (off_t) -1;
	}

	return lseek(VfdCache[fc_file].fd, 0, SEEK_END);
}

int FileTruncate(File fc_file, off_t fc_offset, uint32 fc_wait_event_info)
{
	int			fc_returnCode;

	Assert(FileIsValid(fc_file));

	DO_DB(elog(LOG, "FileTruncate %d (%s)",
			   fc_file, VfdCache[fc_file].fileName));

	fc_returnCode = fc_FileAccess(fc_file);
	if (fc_returnCode < 0)
		return fc_returnCode;

	pgstat_report_wait_start(fc_wait_event_info);
	fc_returnCode = ftruncate(VfdCache[fc_file].fd, fc_offset);
	pgstat_report_wait_end();

	if (fc_returnCode == 0 && VfdCache[fc_file].fileSize > fc_offset)
	{
		/* 调整我们的状态以便截断临时文件 */
		Assert(VfdCache[fc_file].fdstate & FD_TEMP_FILE_LIMIT);
		temporary_files_size -= VfdCache[fc_file].fileSize - fc_offset;
		VfdCache[fc_file].fileSize = fc_offset;
	}

	return fc_returnCode;
}

/*
 * 返回与打开文件关联的路径名。
 *
 * 返回的字符串指向一个内部缓冲区，该缓冲区在文件关闭之前有效。
 */
char * FilePathName(File fc_file)
{
	Assert(FileIsValid(fc_file));

	return VfdCache[fc_file].fileName;
}

/*
 * 返回已打开文件的原始文件描述符。
 *
 * 返回的文件描述符在文件关闭之前有效，但有很多事情可能导致
 * 这发生。因此，调用者在完成使用返回的文件描述符之前应
 * 小心不要做其他事情。
 */
int FileGetRawDesc(File fc_file)
{
	Assert(FileIsValid(fc_file));
	return VfdCache[fc_file].fd;
}

/*
 * FileGetRawFlags - 返回在open(2)上设置的文件标志
 */
int FileGetRawFlags(File fc_file)
{
	Assert(FileIsValid(fc_file));
	return VfdCache[fc_file].fileFlags;
}

/*
 * FileGetRawMode - 返回传递给open(2)的模式位掩码
 */
mode_t
FileGetRawMode(File fc_file)
{
	Assert(FileIsValid(fc_file));
	return VfdCache[fc_file].fileMode;
}

/*
 * 如果需要和可能的话，为另一个allocatedDescs[]数组条目腾出空间。
 * 如果数组元素可用，则返回true。
 */
static bool fc_reserveAllocatedDesc(void)
{
	AllocateDesc *fc_newDescs;
	int			fc_newMax;

	/* 如果数组已经有一个空闲槽则快速返回。 */
	if (numAllocatedDescs < maxAllocatedDescs)
		return true;

	/*
	 * 如果数组尚未在当前进程中创建，请用FD_MINFREE / 3元素初始化它。
	 * 在许多情况下，这正是我们需要的数量。我们不想立即查看max_safe_fds，
	 * 因为set_max_safe_fds()可能还没有运行。
	 */
	if (allocatedDescs == NULL)
	{
		fc_newMax = FD_MINFREE / 3;
		fc_newDescs = (AllocateDesc *) malloc(fc_newMax * sizeof(AllocateDesc));
		/* 已经没有内存了吗？视为致命错误。 */
		if (fc_newDescs == NULL)
			ereport(ERROR,
					(errcode(ERRCODE_OUT_OF_MEMORY),
					 errmsg("out of memory")));
		allocatedDescs = fc_newDescs;
		maxAllocatedDescs = fc_newMax;
		return true;
	}

	/*
	 * 考虑将数组扩展到超出上述初始分配。
	 * 到此时，max_safe_fds应该已被准确知晓。
	 *
	 * 我们不能让分配的描述符占用所有可用的文件描述符，
	 * 而实际上我们最好为VFD使用保留合理数量的FD。
	 * 因此，将最大值设置为max_safe_fds / 3。
	 * （这应该至少与初始大小FD_MINFREE / 3一样大，
	 * 以便我们不在这里收紧限制。）
	 * 请记住，“外部”FD允许消耗最大安全FD的三分之一。
	 */
	fc_newMax = max_safe_fds / 3;
	if (fc_newMax > maxAllocatedDescs)
	{
		fc_newDescs = (AllocateDesc *) realloc(allocatedDescs,
											fc_newMax * sizeof(AllocateDesc));
		/* 将内存不足视为非致命错误。 */
		if (fc_newDescs == NULL)
			return false;
		allocatedDescs = fc_newDescs;
		maxAllocatedDescs = fc_newMax;
		return true;
	}

	/* 不能再扩大allocatedDescs[]了。 */
	return false;
}

/*
 * 需要使用stdio（即文件指针FILE*）的例程应使用AllocateFile，
 * 而不是简单的fopen()。这使得fd.c可以在必要时处理释放FD， 
 * 以打开文件。完成后，请调用FreeFile而不是fclose。
 *
 * 请注意，将要打开很长时间的文件不应这样处理，
 * 因为它们无法与其他文件共享内核文件描述符；
 * 如果有人锁定了太多的FD，将面临耗尽FD的严重风险。
 * 大多数调用此例程的程序只是读取配置文件，
 * 然后立即读取和关闭。
 *
 * fd.c将在事务提交或中止时自动关闭所有使用AllocateFile打开的文件；
 * 这防止了如果调用AllocateFile的例程被ereport(ERROR)提前终止而导致的FD泄漏。
 *
 * 理想情况下，这应该是后端中唯一的直接fopen()调用。
 */
FILE * AllocateFile(const char *fc_name, const char *fc_mode)
{
	FILE	   *fc_file;

	DO_DB(elog(LOG, "AllocateFile: Allocated %d (%s)",
			   numAllocatedDescs, fc_name));

	/* 我们可以再分配一个非虚拟FD吗？ */
	if (!fc_reserveAllocatedDesc())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
				 errmsg("exceeded maxAllocatedDescs (%d) while trying to open file \"%s\"",
						maxAllocatedDescs, fc_name)));

	/* 关闭多余的内核文件描述符。 */
	fc_ReleaseLruFiles();

TryAgain:
	if ((fc_file = fopen(fc_name, fc_mode)) != NULL)
	{
		AllocateDesc *fc_desc = &allocatedDescs[numAllocatedDescs];

		fc_desc->kind = AllocateDescFile;
		fc_desc->desc.file = fc_file;
		fc_desc->create_subid = GetCurrentSubTransactionId();
		numAllocatedDescs++;
		return fc_desc->desc.file;
	}

	if (errno == EMFILE || errno == ENFILE)
	{
		int			fc_save_errno = errno;

		ereport(LOG,
				(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
				 errmsg("out of file descriptors: %m; release and retry")));
		errno = 0;
		if (fc_ReleaseLruFile())
			goto TryAgain;
		errno = fc_save_errno;
	}

	return NULL;
}


/*
 * 使用 OpenTransientFilePerm() 打开文件，并传递默认文件模式作为
 * fileMode 参数。
 */
int OpenTransientFile(const char *fc_fileName, int fc_fileFlags)
{
	return OpenTransientFilePerm(fc_fileName, fc_fileFlags, pg_file_create_mode);
}

/*
 * 像 AllocateFile，但返回一个未缓冲的文件描述符，类似于 open(2)
 */
int OpenTransientFilePerm(const char *fc_fileName, int fc_fileFlags, mode_t fc_fileMode)
{
	int			fc_fd;

	DO_DB(elog(LOG, "OpenTransientFile: Allocated %d (%s)",
			   numAllocatedDescs, fc_fileName));

	/* 我们可以再分配一个非虚拟FD吗？ */
	if (!fc_reserveAllocatedDesc())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
				 errmsg("exceeded maxAllocatedDescs (%d) while trying to open file \"%s\"",
						maxAllocatedDescs, fc_fileName)));

	/* 关闭多余的内核文件描述符。 */
	fc_ReleaseLruFiles();

	fc_fd = BasicOpenFilePerm(fc_fileName, fc_fileFlags, fc_fileMode);

	if (fc_fd >= 0)
	{
		AllocateDesc *fc_desc = &allocatedDescs[numAllocatedDescs];

		fc_desc->kind = AllocateDescRawFD;
		fc_desc->desc.fd = fc_fd;
		fc_desc->create_subid = GetCurrentSubTransactionId();
		numAllocatedDescs++;

		return fc_fd;
	}

	return -1;					/* 失败 */
}

/*
 * 希望启动管道流的例程应该使用 OpenPipeStream 
 * 而不是普通的 popen()。这让 fd.c 在必要时处理 FD 的释放。
 * 完成后，应调用 ClosePipeStream 而不是 pclose。
 *
 * 此函数还确保 popen 的程序以默认的 SIGPIPE 处理运行，而不是后端通常
 * 使用的 SIG_IGN 设置。这确保对例如提前关闭读管道的良好响应。
 */
FILE * OpenPipeStream(const char *fc_command, const char *fc_mode)
{
	FILE	   *fc_file;
	int			fc_save_errno;

	DO_DB(elog(LOG, "OpenPipeStream: Allocated %d (%s)",
			   numAllocatedDescs, fc_command));

	/* 我们可以再分配一个非虚拟FD吗？ */
	if (!fc_reserveAllocatedDesc())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
				 errmsg("exceeded maxAllocatedDescs (%d) while trying to execute command \"%s\"",
						maxAllocatedDescs, fc_command)));

	/* 关闭多余的内核文件描述符。 */
	fc_ReleaseLruFiles();

TryAgain:
	fflush(stdout);
	fflush(stderr);
	pqsignal(SIGPIPE, SIG_DFL);
	errno = 0;
	fc_file = popen(fc_command, fc_mode);
	fc_save_errno = errno;
	pqsignal(SIGPIPE, SIG_IGN);
	errno = fc_save_errno;
	if (fc_file != NULL)
	{
		AllocateDesc *fc_desc = &allocatedDescs[numAllocatedDescs];

		fc_desc->kind = AllocateDescPipe;
		fc_desc->desc.file = fc_file;
		fc_desc->create_subid = GetCurrentSubTransactionId();
		numAllocatedDescs++;
		return fc_desc->desc.file;
	}

	if (errno == EMFILE || errno == ENFILE)
	{
		ereport(LOG,
				(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
				 errmsg("out of file descriptors: %m; release and retry")));
		if (fc_ReleaseLruFile())
			goto TryAgain;
		errno = fc_save_errno;
	}

	return NULL;
}

/*
 * 释放任何类型的 AllocateDesc。
 *
 * 参数 *必须* 指向 allocatedDescs[] 数组中。
 */
static int fc_FreeDesc(AllocateDesc *fc_desc)
{
	int			fc_result;

	/* 关闭底层对象 */
	switch (fc_desc->kind)
	{
		case AllocateDescFile:
			fc_result = fclose(fc_desc->desc.file);
			break;
		case AllocateDescPipe:
			fc_result = pclose(fc_desc->desc.file);
			break;
		case AllocateDescDir:
			fc_result = closedir(fc_desc->desc.dir);
			break;
		case AllocateDescRawFD:
			fc_result = close(fc_desc->desc.fd);
			break;
		default:
			elog(ERROR, "AllocateDesc kind not recognized");
			fc_result = 0;			/* 保持编译器安静 */
			break;
	}

	/* 在 allocatedDescs 数组中压缩存储 */
	numAllocatedDescs--;
	*fc_desc = allocatedDescs[numAllocatedDescs];

	return fc_result;
}

/*
 * 关闭由 AllocateFile 返回的文件。
 *
 * 注意，我们不检查 fclose 的返回值 --- 处理关闭错误的责任在于调用者。
 */
int FreeFile(FILE *fc_file)
{
	int			fc_i;

	DO_DB(elog(LOG, "FreeFile: Allocated %d", numAllocatedDescs));

	/* 从已分配文件列表中移除文件（如果存在） */
	for (fc_i = numAllocatedDescs; --fc_i >= 0;)
	{
		AllocateDesc *fc_desc = &allocatedDescs[fc_i];

		if (fc_desc->kind == AllocateDescFile && fc_desc->desc.file == fc_file)
			return fc_FreeDesc(fc_desc);
	}

	/* 只有在有人传递给我们一个不在 allocatedDescs 中的文件时才能到这里 */
	elog(WARNING, "file passed to FreeFile was not obtained from AllocateFile");

	return fclose(fc_file);
}

/*
 * 关闭由 OpenTransientFile 返回的文件。
 *
 * 注意，我们不检查 close 的返回值 --- 处理关闭错误的责任在于调用者。
 */
int CloseTransientFile(int fc_fd)
{
	int			fc_i;

	DO_DB(elog(LOG, "CloseTransientFile: Allocated %d", numAllocatedDescs));

	/* 从已分配文件列表中移除 fd（如果存在） */
	for (fc_i = numAllocatedDescs; --fc_i >= 0;)
	{
		AllocateDesc *fc_desc = &allocatedDescs[fc_i];

		if (fc_desc->kind == AllocateDescRawFD && fc_desc->desc.fd == fc_fd)
			return fc_FreeDesc(fc_desc);
	}

	/* 只有在有人传递给我们一个不在 allocatedDescs 中的文件时才能到这里 */
	elog(WARNING, "fd passed to CloseTransientFile was not obtained from OpenTransientFile");

	return close(fc_fd);
}

/*
 * 想要使用 <dirent.h>（即 DIR*）的例程应该使用 AllocateDir
 * 而不是普通的 opendir()。这使得 fd.c 可以在必要时处理打开目录的 FD 释放，并在 elog 后关闭它。
 * 完成后，调用 FreeDir 而不是 closedir。
 *
 * 失败时返回 NULL，errno 会被设置。注意，失败检测通常留给随后的 ReadDir 或 ReadDirExtended 调用；
 * 参见 ReadDir 的注释。
 *
 * 理想情况下，这应该是后端中对 opendir() 的 *唯一* 直接调用。
 */
DIR *
AllocateDir(const char *fc_dirname)
{
	DIR		   *fc_dir;

	DO_DB(elog(LOG, "AllocateDir: Allocated %d (%s)",
			   numAllocatedDescs, fc_dirname));

	/* 我们可以再分配一个非虚拟FD吗？ */
	if (!fc_reserveAllocatedDesc())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
				 errmsg("exceeded maxAllocatedDescs (%d) while trying to open directory \"%s\"",
						maxAllocatedDescs, fc_dirname)));

	/* 关闭多余的内核文件描述符。 */
	fc_ReleaseLruFiles();

TryAgain:
	if ((fc_dir = opendir(fc_dirname)) != NULL)
	{
		AllocateDesc *fc_desc = &allocatedDescs[numAllocatedDescs];

		fc_desc->kind = AllocateDescDir;
		fc_desc->desc.dir = fc_dir;
		fc_desc->create_subid = GetCurrentSubTransactionId();
		numAllocatedDescs++;
		return fc_desc->desc.dir;
	}

	if (errno == EMFILE || errno == ENFILE)
	{
		int			fc_save_errno = errno;

		ereport(LOG,
				(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
				 errmsg("out of file descriptors: %m; release and retry")));
		errno = 0;
		if (fc_ReleaseLruFile())
			goto TryAgain;
		errno = fc_save_errno;
	}

	return NULL;
}

/*
 * 读取一个通过 AllocateDir 打开的目录，并报告任何错误。
 *
 * 这比原始的 readdir() 更容易使用，因为它处理了一些
 * 否则相当繁琐且容易出错的 errno 操作。此外，
 * 如果你对 AllocateDir 失败的通用错误消息感到满意，
 * 你可以简单地这样做：
 *
 *		dir = AllocateDir(path);
 *		while ((dirent = ReadDir(dir, path)) != NULL)
 *			process dirent;
 *		FreeDir(dir);
 *
 * 因为 NULL dir 参数被视为指示 AllocateDir 失败。
 * （如果你使用这个快捷方式，确保在 AllocateDir 和 ReadDir 之间 errno 没有变化。）
 *
 * 传递给 AllocateDir 的路径名也必须传递给此例程，
 * 但它仅用于错误报告。
 */
struct dirent * ReadDir(DIR *fc_dir, const char *fc_dirname)
{
	return ReadDirExtended(fc_dir, fc_dirname, ERROR);
}

/*
 * ReadDir 的替代版本，允许调用者为任何错误报告指定 elevel
 * （无论是报告 AllocateDir 的初始失败还是随后的目录读取失败）。
 *
 * 如果 elevel < ERROR，在任何错误后返回 NULL。根据正常的编码
 * 模式，这将导致立即退出循环，就好像目录没有（更多）条目一样。
 */
struct dirent * ReadDirExtended(DIR *fc_dir, const char *fc_dirname, int fc_elevel)
{
	struct dirent *fc_dent;

	/* 如果调用者没有给出，提供一个 AllocateDir 失败的通用消息 */
	if (fc_dir == NULL)
	{
		ereport(fc_elevel,
				(errcode_for_file_access(),
				 errmsg("could not open directory \"%s\": %m",
						fc_dirname)));
		return NULL;
	}

	errno = 0;
	if ((fc_dent = readdir(fc_dir)) != NULL)
		return fc_dent;

	if (errno)
		ereport(fc_elevel,
				(errcode_for_file_access(),
				 errmsg("could not read directory \"%s\": %m",
						fc_dirname)));
	return NULL;
}

/*
 * 关闭通过 AllocateDir 打开的目录。
 *
 * 返回 closedir 的返回值（如果不为 0，则设置 errno）。
 * 注意，我们不检查返回值 --- 如果需要，处理关闭错误的责任在于调用者。
 *
 * 如果 dir == NULL 则不执行任何操作；我们假设如果需要已经报告了目录打开失败。
 */
int FreeDir(DIR *fc_dir)
{
	int			fc_i;

	/* 如果 AllocateDir 失败，则没有任何事情可做 */
	if (fc_dir == NULL)
		return 0;

	DO_DB(elog(LOG, "FreeDir: Allocated %d", numAllocatedDescs));

	/* 从已分配的目录列表中移除 dir（如果存在） */
	for (fc_i = numAllocatedDescs; --fc_i >= 0;)
	{
		AllocateDesc *fc_desc = &allocatedDescs[fc_i];

		if (fc_desc->kind == AllocateDescDir && fc_desc->desc.dir == fc_dir)
			return fc_FreeDesc(fc_desc);
	}

	/* 只有在有人传递给我们一个不在 allocatedDescs 中的目录时才能到这里 */
	elog(WARNING, "dir passed to FreeDir was not obtained from AllocateDir");

	return closedir(fc_dir);
}


/*
 * 关闭由 OpenPipeStream 返回的管道流。
 */
int ClosePipeStream(FILE *fc_file)
{
	int			fc_i;

	DO_DB(elog(LOG, "ClosePipeStream: Allocated %d", numAllocatedDescs));

	/* 从已分配文件列表中移除文件（如果存在） */
	for (fc_i = numAllocatedDescs; --fc_i >= 0;)
	{
		AllocateDesc *fc_desc = &allocatedDescs[fc_i];

		if (fc_desc->kind == AllocateDescPipe && fc_desc->desc.file == fc_file)
			return fc_FreeDesc(fc_desc);
	}

	/* 只有在有人传递给我们一个不在 allocatedDescs 中的文件时才能到这里 */
	elog(WARNING, "file passed to ClosePipeStream was not obtained from OpenPipeStream");

	return pclose(fc_file);
}

/*
 * closeAllVfds
 *
 * 强制所有 VFD 进入物理关闭状态，以便最少数量的内核文件描述符在使用中。逻辑状态不变。
 */
void closeAllVfds(void)
{
	Index		fc_i;

	if (SizeVfdCache > 0)
	{
		Assert(FileIsNotOpen(0));	/* 确保环没有损坏 */
		for (fc_i = 1; fc_i < SizeVfdCache; fc_i++)
		{
			if (!FileIsNotOpen(fc_i))
				fc_LruDelete(fc_i);
		}
	}
}


/*
 * SetTempTablespaces
 *
 * 定义一个用于临时文件的表空间 OID 列表（实际上是一个数组）。该列表将一直使用到事务结束，
 * 除非在此之前再次调用该函数。调用者有责任确保传入的数组具有足够的生命周期（通常
 * 它会在 TopTransactionContext 中分配）。
 *
 * 数组中的某些条目可能是 InvalidOid，表示应使用当前数据库的默认表空间。
 */
void SetTempTablespaces(Oid *fc_tableSpaces, int fc_numSpaces)
{
	Assert(fc_numSpaces >= 0);
	tempTableSpaces = fc_tableSpaces;
	numTempTableSpaces = fc_numSpaces;

	/*
	 * 在列表中选择一个随机起始点。这是为了最小化
	 * 在最可能共享相同临时表空间列表的后端之间的冲突。注意，如果我们在同一个事务中创建多个临时文件，
	 * 我们将循环遍历列表 --- 这确保大型临时排序文件均匀分布在所有可用表空间中。
	 */
	if (fc_numSpaces > 1)
		nextTempTableSpace = pg_prng_uint64_range(&pg_global_prng_state,
												  0, fc_numSpaces - 1);
	else
		nextTempTableSpace = 0;
}

/*
 * TempTablespacesAreSet
 *
 * 如果在当前事务中调用了 SetTempTablespaces，则返回 true。
 * （这只是为了使 tablespaces.c 不需要其自己的每个事务
 * 状态。）
 */
bool TempTablespacesAreSet(void)
{
	return (numTempTableSpaces >= 0);
}


/*
 * GetTempTablespaces
 *
 * 通过填充一个数组，获取应该用于临时文件的表空间的OID。（某些条目可能是InvalidOid，表示应使用当前数据库的默认表空间。）最多会填充numSpaces个条目。
 * 返回复制到输出数组中的OID数量。
 */
int GetTempTablespaces(Oid *fc_tableSpaces, int fc_numSpaces)
{
	int			fc_i;

	Assert(TempTablespacesAreSet());
	for (fc_i = 0; fc_i < numTempTableSpaces && fc_i < fc_numSpaces; ++fc_i)
		fc_tableSpaces[fc_i] = tempTableSpaces[fc_i];

	return fc_i;
}

/*
 * GetNextTempTableSpace
 *
 * 选择下一个要使用的临时表空间。InvalidOid的结果意味着使用当前数据库的默认表空间。
 */
Oid GetNextTempTableSpace(void)
{
	if (numTempTableSpaces > 0)
	{
		/* 通过回绕来推进nextTempTableSpace计数器 */
		if (++nextTempTableSpace >= numTempTableSpaces)
			nextTempTableSpace = 0;
		return tempTableSpaces[nextTempTableSpace];
	}
	return InvalidOid;
}


/*
 * AtEOSubXact_Files
 *
 * 处理子事务的提交/中止。在中止时，我们关闭子事务可能已经打开的临时文件。在提交时，我们将已打开的文件重新分配给父子事务。
 */
void AtEOSubXact_Files(bool fc_isCommit, SubTransactionId fc_mySubid,
				  SubTransactionId fc_parentSubid)
{
	Index		fc_i;

	for (fc_i = 0; fc_i < numAllocatedDescs; fc_i++)
	{
		if (allocatedDescs[fc_i].create_subid == fc_mySubid)
		{
			if (fc_isCommit)
				allocatedDescs[fc_i].create_subid = fc_parentSubid;
			else
			{
				/* 在FreeDesc后必须重新检查该项（很丑） */
				fc_FreeDesc(&allocatedDescs[fc_i--]);
			}
		}
	}
}

/*
 * AtEOXact_Files
 *
 * 在事务提交或中止期间调用此例程。所有仍然打开的每个事务临时文件VFD都会被关闭，这也会导致底层文件被删除（尽管它们应该已经被资源拥有者清理关闭）。此外，所有“已分配”的stdio文件也会被关闭。我们还忘记任何事务本地的临时表空间列表。
 *
 * isCommit标志仅用于决定是否发出有关未关闭文件的警告。
 */
void AtEOXact_Files(bool fc_isCommit)
{
	fc_CleanupTempFiles(fc_isCommit, false);
	tempTableSpaces = NULL;
	numTempTableSpaces = -1;
}

/*
 * BeforeShmemExit_Files
 *
 * before_shmem_access 钩子在后端关闭时清理临时文件。
 * 在这里，我们想要清理 *所有* 临时文件，包括 interXact 文件。
 */
static void fc_BeforeShmemExit_Files(int fc_code, Datum fc_arg)
{
	fc_CleanupTempFiles(false, true);

	/* 防止进一步创建临时文件 */
#ifdef USE_ASSERT_CHECKING
	temporary_files_allowed = false;
#endif
}

/*
 * 关闭临时文件并删除其底层文件。
 *
 * isCommit: 如果为真，这是正常的事务提交，我们不
 * 期望有任何剩余文件；如果有，发出警告。
 *
 * isProcExit: 如果为真，这在后端进程退出时被调用。
 * 如果是这种情况，我们应该移除所有临时文件；如果
 * 不是这种情况，我们是在事务提交/中止时被调用
 * 并且只应移除事务本地的临时文件。在任何一种情况下，
 * 还要清理 "分配的" stdio 文件、目录和文件描述符。
 */
static void fc_CleanupTempFiles(bool fc_isCommit, bool fc_isProcExit)
{
	Index		fc_i;

	/*
	 * 小心：在 proc_exit 时我们需要额外的清理，而不仅仅是
	 * xact_temporary 文件。
	 */
	if (fc_isProcExit || have_xact_temporary_files)
	{
		Assert(FileIsNotOpen(0));	/* 确保环没有损坏 */
		for (fc_i = 1; fc_i < SizeVfdCache; fc_i++)
		{
			unsigned short fc_fdstate = VfdCache[fc_i].fdstate;

			if (((fc_fdstate & FD_DELETE_AT_CLOSE) || (fc_fdstate & FD_CLOSE_AT_EOXACT)) &&
				VfdCache[fc_i].fileName != NULL)
			{
				/*
				 * 如果我们正在退出后端进程，则关闭
				 * 所有临时文件。否则，仅关闭当前事务
				 * 本地的临时文件。它们应该已经由
				 * ResourceOwner 机制关闭，因此这只是一个
				 * 调试交叉检查。
				 */
				if (fc_isProcExit)
					FileClose(fc_i);
				else if (fc_fdstate & FD_CLOSE_AT_EOXACT)
				{
					elog(WARNING,
						 "temporary file %s not closed at end-of-transaction",
						 VfdCache[fc_i].fileName);
					FileClose(fc_i);
				}
			}
		}

		have_xact_temporary_files = false;
	}

	/* 如果在提交时仍有任何分配的文件打开，则投诉。 */
	if (fc_isCommit && numAllocatedDescs > 0)
		elog(WARNING, "%d temporary files and directories not closed at end-of-transaction",
			 numAllocatedDescs);

	/* 清理 "分配的" stdio 文件、目录和文件描述符。 */
	while (numAllocatedDescs > 0)
		fc_FreeDesc(&allocatedDescs[0]);
}


/*
 * 移除来自之前 postmaster 会话的临时和临时关系文件
 *
 * 这应该在 postmaster 启动期间调用。它将强制
 * 移除由 OpenTemporaryFile 创建的任何剩余文件和由
 * mdcreate 创建的任何剩余临时关系文件。
 *
 * 在后端崩溃重启周期期间，当 remove_temp_files_after_crash GUC 被启用时调用
 * 此例程。多次崩溃而查询使用临时文件可能会导致
 * 无用的存储使用，只能通过服务重启回收。反对启用它的理由是
 * 有人可能希望出于调试目的检查临时文件。然而，这意味着
 * OpenTemporaryFile 最好允许与现有临时文件名发生冲突。
 *
 * 注意：这个函数及其子例程通常通过 ereport(LOG) 报告系统调用失败
 * 并继续进行。移除临时文件并不是如此关键
 * 以至于我们不能做到这一点而无法启动数据库。
 */
void RemovePgTempFiles(void)
{
	char		fc_temp_path[MAXPGPATH + 10 + sizeof(TABLESPACE_VERSION_DIRECTORY) + sizeof(PG_TEMP_FILES_DIR)];
	DIR		   *fc_spc_dir;
	struct dirent *fc_spc_de;

	/*
	 * 首先处理 pg_default ($PGDATA/base) 中的临时文件
	 */
	snprintf(fc_temp_path, sizeof(fc_temp_path), "base/%s", PG_TEMP_FILES_DIR);
	RemovePgTempFilesInDir(fc_temp_path, true, false);
	fc_RemovePgTempRelationFiles("base");

	/*
	 * 遍历所有非默认表空间的临时目录。
	 */
	fc_spc_dir = AllocateDir("pg_tblspc");

	while ((fc_spc_de = ReadDirExtended(fc_spc_dir, "pg_tblspc", LOG)) != NULL)
	{
		if (strcmp(fc_spc_de->d_name, ".") == 0 ||
			strcmp(fc_spc_de->d_name, "..") == 0)
			continue;

		snprintf(fc_temp_path, sizeof(fc_temp_path), "pg_tblspc/%s/%s/%s",
				 fc_spc_de->d_name, TABLESPACE_VERSION_DIRECTORY, PG_TEMP_FILES_DIR);
		RemovePgTempFilesInDir(fc_temp_path, true, false);

		snprintf(fc_temp_path, sizeof(fc_temp_path), "pg_tblspc/%s/%s",
				 fc_spc_de->d_name, TABLESPACE_VERSION_DIRECTORY);
		fc_RemovePgTempRelationFiles(fc_temp_path);
	}

	FreeDir(fc_spc_dir);

	/*
	 * 在 EXEC_BACKEND 情况下，DataDir 顶层还有一个 pgsql_tmp 目录。
	 * 然而，在这里 *不* 进行清理，因为这样做会导致竞争条件。
	 * 这在 postmaster 启动的早期单独完成。
	 */
}

/*
 * 处理 RemovePgTempFiles 的一个 pgsql_tmp 目录。
 *
 * 如果 missing_ok 为真，则命名目录不存在也是可以的。
 * 任何其他问题都会导致记录一条日志消息。 （在
 * 顶层，missing_ok 应该为真，因为 pgsql_tmp 目录
 * 直到需要时才会创建。）
 *
 * 在顶层，这应该用 unlink_all = false 调用，以便
 * 仅 unlink 匹配临时名称前缀的文件。当
 * 递归时，它将以 unlink_all = true 调用，以 unlink
 * 顶层临时目录下的所有内容。
 *
 * （这两个标志可以用一个替代，但保持它们分开似乎更清晰。）
 */
void RemovePgTempFilesInDir(const char *fc_tmpdirname, bool fc_missing_ok, bool fc_unlink_all)
{
	DIR		   *fc_temp_dir;
	struct dirent *fc_temp_de;
	char		fc_rm_path[MAXPGPATH * 2];

	fc_temp_dir = AllocateDir(fc_tmpdirname);

	if (fc_temp_dir == NULL && errno == ENOENT && fc_missing_ok)
		return;

	while ((fc_temp_de = ReadDirExtended(fc_temp_dir, fc_tmpdirname, LOG)) != NULL)
	{
		if (strcmp(fc_temp_de->d_name, ".") == 0 ||
			strcmp(fc_temp_de->d_name, "..") == 0)
			continue;

		snprintf(fc_rm_path, sizeof(fc_rm_path), "%s/%s",
				 fc_tmpdirname, fc_temp_de->d_name);

		if (fc_unlink_all ||
			strncmp(fc_temp_de->d_name,
					PG_TEMP_FILE_PREFIX,
					strlen(PG_TEMP_FILE_PREFIX)) == 0)
		{
			struct stat fc_statbuf;

			if (lstat(fc_rm_path, &fc_statbuf) < 0)
			{
				ereport(LOG,
						(errcode_for_file_access(),
						 errmsg("could not stat file \"%s\": %m", fc_rm_path)));
				continue;
			}

			if (S_ISDIR(fc_statbuf.st_mode))
			{
				/* 递归删除内容，然后是目录本身 */
				RemovePgTempFilesInDir(fc_rm_path, false, true);

				if (rmdir(fc_rm_path) < 0)
					ereport(LOG,
							(errcode_for_file_access(),
							 errmsg("could not remove directory \"%s\": %m",
									fc_rm_path)));
			}
			else
			{
				if (unlink(fc_rm_path) < 0)
					ereport(LOG,
							(errcode_for_file_access(),
							 errmsg("could not remove file \"%s\": %m",
									fc_rm_path)));
			}
		}
		else
			ereport(LOG,
					(errmsg("unexpected file found in temporary-files directory: \"%s\"",
							fc_rm_path)));
	}

	FreeDir(fc_temp_dir);
}

/* 处理一个表空间目录，查找每个数据库的子目录 */
static void fc_RemovePgTempRelationFiles(const char *fc_tsdirname)
{
	DIR		   *fc_ts_dir;
	struct dirent *fc_de;
	char		fc_dbspace_path[MAXPGPATH * 2];

	fc_ts_dir = AllocateDir(fc_tsdirname);

	while ((fc_de = ReadDirExtended(fc_ts_dir, fc_tsdirname, LOG)) != NULL)
	{
		/*
		 * 我们只对具有数字名称的每个数据库目录感兴趣。
		 * 请注意，这段代码也会（正确地）忽略 "."
		 * 和 ".."。
		 */
		if (strspn(fc_de->d_name, "0123456789") != strlen(fc_de->d_name))
			continue;

		snprintf(fc_dbspace_path, sizeof(fc_dbspace_path), "%s/%s",
				 fc_tsdirname, fc_de->d_name);
		fc_RemovePgTempRelationFilesInDbspace(fc_dbspace_path);
	}

	FreeDir(fc_ts_dir);
}

/* 处理 RemovePgTempRelationFiles 的一个 per-dbspace 目录 */
static void fc_RemovePgTempRelationFilesInDbspace(const char *fc_dbspacedirname)
{
	DIR		   *fc_dbspace_dir;
	struct dirent *fc_de;
	char		fc_rm_path[MAXPGPATH * 2];

	fc_dbspace_dir = AllocateDir(fc_dbspacedirname);

	while ((fc_de = ReadDirExtended(fc_dbspace_dir, fc_dbspacedirname, LOG)) != NULL)
	{
		if (!looks_like_temp_rel_name(fc_de->d_name))
			continue;

		snprintf(fc_rm_path, sizeof(fc_rm_path), "%s/%s",
				 fc_dbspacedirname, fc_de->d_name);

		if (unlink(fc_rm_path) < 0)
			ereport(LOG,
					(errcode_for_file_access(),
					 errmsg("could not remove file \"%s\": %m",
							fc_rm_path)));
	}

	FreeDir(fc_dbspace_dir);
}

/* t<digits>_<digits>，或 t<digits>_<digits>_<forkname> */
bool looks_like_temp_rel_name(const char *fc_name)
{
	int			fc_pos;
	int			fc_savepos;

	/* 必须以 "t" 开头。 */
	if (fc_name[0] != 't')
		return false;

	/* 后面跟一个非空的数字字符串，然后是一个下划线。 */
	for (fc_pos = 1; isdigit((unsigned char) fc_name[fc_pos]); ++fc_pos)
		;
	if (fc_pos == 1 || fc_name[fc_pos] != '_')
		return false;

	/* 然后再跟另一个非空的数字字符串。 */
	for (fc_savepos = ++fc_pos; isdigit((unsigned char) fc_name[fc_pos]); ++fc_pos)
		;
	if (fc_savepos == fc_pos)
		return false;

	/* 我们可能有 _forkname 或 .segment 或两者都有。 */
	if (fc_name[fc_pos] == '_')
	{
		int			fc_forkchar = forkname_chars(&fc_name[fc_pos + 1], NULL);

		if (fc_forkchar <= 0)
			return false;
		fc_pos += fc_forkchar + 1;
	}
	if (fc_name[fc_pos] == '.')
	{
		int			fc_segchar;

		for (fc_segchar = 1; isdigit((unsigned char) fc_name[fc_pos + fc_segchar]); ++fc_segchar)
			;
		if (fc_segchar <= 1)
			return false;
		fc_pos += fc_segchar;
	}

	/* 现在我们应该处于末尾。 */
	if (fc_name[fc_pos] != '\0')
		return false;
	return true;
}

#ifdef HAVE_SYNCFS
static void fc_do_syncfs(const char *fc_path)
{
	int			fc_fd;

	ereport_startup_progress("syncing data directory (syncfs), elapsed time: %ld.%02d s, current path: %s",
							 fc_path);

	fc_fd = OpenTransientFile(fc_path, O_RDONLY);
	if (fc_fd < 0)
	{
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not open file \"%s\": %m", fc_path)));
		return;
	}
	if (syncfs(fc_fd) < 0)
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not synchronize file system for file \"%s\": %m", fc_path)));
	CloseTransientFile(fc_fd);
}
#endif


/*
 * 递归地对 PGDATA 及其所有内容发出 fsync，或根据 recovery_init_sync_method 设置为所有潜在文件系统发出 syncfs。
 *
 * 我们会对常规文件和目录执行 fsync，不论它们在哪里，但我们仅对 pg_wal 和 pg_tblspc 下的立即子目录跟随符号链接。
 * 其他符号链接假定指向我们不负责 fsync 的文件，并且可能没有写权限。
 *
 * 错误被记录但不被视为致命；这是因为该操作仅在数据库启动期间使用，以处理可能存在待处理的已发出但未同步的写入数据目录的情况。
 * 我们希望确保这些写入在新运行中完成任何操作之前就到达磁盘。然而，因为在错误时中止将导致对于数据目录中只读文件等无害情况的启动失败，这也不好。
 *
 * 请注意，如果我们之前因 fsync() 的 PANIC 崩溃，那么在恢复期间将重新写入所有更改。
 *
 * 请注意，我们假设一开始就已经切换到了 PGDATA。
 */
void SyncDataDirectory(void)
{
	bool		fc_xlog_is_symlink;

	/* 如果 fsync 被禁用，我们可以跳过整个过程。 */
	if (!enableFsync)
		return;

	/*
	 * 如果 pg_wal 是一个符号链接，我们需要单独递归进入它，
	 * 因为下面的第一个 walkdir 会忽略它。
	 */
	fc_xlog_is_symlink = false;

	{
		struct stat fc_st;

		if (lstat("pg_wal", &fc_st) < 0)
			ereport(LOG,
					(errcode_for_file_access(),
					 errmsg("could not stat file \"%s\": %m",
							"pg_wal")));
		else if (S_ISLNK(fc_st.st_mode))
			fc_xlog_is_symlink = true;
	}

#ifdef HAVE_SYNCFS
	if (recovery_init_sync_method == RECOVERY_INIT_SYNC_METHOD_SYNCFS)
	{
		DIR		   *fc_dir;
		struct dirent *fc_de;

		/*
		 * 在 Linux 上，我们不必逐个打开每个文件。我们可以使用 syncfs() 来同步整个文件系统。
		 * 我们仅期望在我们容忍符号链接的地方存在文件系统边界，即 pg_wal 和表空间，因此我们对每个目录调用 syncfs()。
		 */

		/* 准备通过 syncfs 报告同步数据目录的进度。 */
		begin_startup_progress_phase();

		/* 同步顶级 pgdata 目录。 */
		fc_do_syncfs(".");
		/* 如果配置了任何表空间，则同步每个表空间。 */
		fc_dir = AllocateDir("pg_tblspc");
		while ((fc_de = ReadDirExtended(fc_dir, "pg_tblspc", LOG)))
		{
			char		fc_path[MAXPGPATH];

			if (strcmp(fc_de->d_name, ".") == 0 || strcmp(fc_de->d_name, "..") == 0)
				continue;

			snprintf(fc_path, MAXPGPATH, "pg_tblspc/%s", fc_de->d_name);
			fc_do_syncfs(fc_path);
		}
		FreeDir(fc_dir);
		/* 如果 pg_wal 是符号链接，也处理该链接。 */
		if (fc_xlog_is_symlink)
			fc_do_syncfs("pg_wal");
		return;
	}
#endif							/* !HAVE_SYNCFS */

#ifdef PG_FLUSH_DATA_WORKS
	/* 准备报告预同步阶段的进度。 */
	begin_startup_progress_phase();

	/*
	 * 如果可能，向内核提示我们即将对数据目录及其内容进行 fsync。
	 * 该步骤中的错误甚至比正常情况更不重要，因此仅在 DEBUG1 级别下记录这些错误。
	 */
	fc_walkdir(".", fc_pre_sync_fname, false, DEBUG1);
	if (fc_xlog_is_symlink)
		fc_walkdir("pg_wal", fc_pre_sync_fname, false, DEBUG1);
	fc_walkdir("pg_tblspc", fc_pre_sync_fname, true, DEBUG1);
#endif

	/* 准备报告通过 fsync 同步数据目录的进度。 */
	begin_startup_progress_phase();

	/*
	 * 现在我们以相同的顺序执行 fsync()。
	 *
	 * 主要调用会忽略符号链接，因此除了在 pg_wal 是符号链接时进行特别处理外，
	 * pg_tblspc 必须单独访问，process_symlinks = true。请注意，如果 pg_tblspc 中有任何普通目录，
	 * 它们将被 fsync 两次。这不是预期的情况，所以我们不担心优化它。
	 */
	fc_walkdir(".", fc_datadir_fsync_fname, false, LOG);
	if (fc_xlog_is_symlink)
		fc_walkdir("pg_wal", fc_datadir_fsync_fname, false, LOG);
	fc_walkdir("pg_tblspc", fc_datadir_fsync_fname, true, LOG);
}

/*
 * walkdir：递归遍历目录，对每个常规文件和目录（包括指定的目录本身）应用操作。
 *
 * 如果 process_symlinks 为真，则还会将操作和递归应用于由给定目录中的符号链接指向的常规文件和目录；否则将忽略符号链接。在子目录中始终忽略符号链接，即我们故意不将 process_symlinks 标志传递给递归调用。
 *
 * 错误以级别 elevel 报告，可以是 ERROR 或更低。
 *
 * 另请参见 file_utils.c 中的 walkdir，它是此逻辑的前端版本。
 */
static void fc_walkdir(const char *fc_path,
		void (*fc_action) (const char *fname, bool isdir, int elevel),
		bool fc_process_symlinks,
		int fc_elevel)
{
	DIR		   *fc_dir;
	struct dirent *fc_de;

	fc_dir = AllocateDir(fc_path);

	while ((fc_de = ReadDirExtended(fc_dir, fc_path, fc_elevel)) != NULL)
	{
		char		fc_subpath[MAXPGPATH * 2];

		CHECK_FOR_INTERRUPTS();

		if (strcmp(fc_de->d_name, ".") == 0 ||
			strcmp(fc_de->d_name, "..") == 0)
			continue;

		snprintf(fc_subpath, sizeof(fc_subpath), "%s/%s", fc_path, fc_de->d_name);

		switch (get_dirent_type(fc_subpath, fc_de, fc_process_symlinks, fc_elevel))
		{
			case PGFILETYPE_REG:
				(*fc_action) (fc_subpath, false, fc_elevel);
				break;
			case PGFILETYPE_DIR:
				fc_walkdir(fc_subpath, fc_action, false, fc_elevel);
				break;
			default:

				/*
				 * 错误已由 get_dirent_type() 直接报告，
				 * 任何剩余的符号链接和未知文件类型都会被忽略。
				 */
				break;
		}
	}

	FreeDir(fc_dir);				/* 我们在此处忽略任何错误 */

	/*
	 * 重要的是对目标目录本身进行 fsync，因为单个文件的 fsync 并不能保证文件的目录项已同步。
	 * 如果 AllocateDir 失败，则跳过此操作；操作函数可能对此不够健壮。
	 */
	if (fc_dir)
		(*fc_action) (fc_path, true, fc_elevel);
}


/*
 * 向操作系统提示应该准备好对该文件进行 fsync()。
 *
 * 忽略尝试打开不可读文件时的错误，并在调用者指定的级别记录其他错误。
 */
#ifdef PG_FLUSH_DATA_WORKS

static void fc_pre_sync_fname(const char *fc_fname, bool fc_isdir, int fc_elevel)
{
	int			fc_fd;

	/* 不要尝试刷新目录，这可能会失败 */
	if (fc_isdir)
		return;

	ereport_startup_progress("syncing data directory (pre-fsync), elapsed time: %ld.%02d s, current path: %s",
							 fc_fname);

	fc_fd = OpenTransientFile(fc_fname, O_RDONLY | PG_BINARY);

	if (fc_fd < 0)
	{
		if (errno == EACCES)
			return;
		ereport(fc_elevel,
				(errcode_for_file_access(),
				 errmsg("could not open file \"%s\": %m", fc_fname)));
		return;
	}

	/*
	 * pg_flush_data() 忽略错误，这没关系，因为这只是一个提示。
	 */
	pg_flush_data(fc_fd, 0, 0);

	if (CloseTransientFile(fc_fd) != 0)
		ereport(fc_elevel,
				(errcode_for_file_access(),
				 errmsg("could not close file \"%s\": %m", fc_fname)));
}

#endif							/* PG_FLUSH_DATA_WORKS */

static void fc_datadir_fsync_fname(const char *fc_fname, bool fc_isdir, int fc_elevel)
{
	ereport_startup_progress("syncing data directory (fsync), elapsed time: %ld.%02d s, current path: %s",
							 fc_fname);

	/*
	 * 我们想要悄悄地忽略关于不可读文件的错误。将该需求传递给 fsync_fname_ext()。
	 */
	fsync_fname_ext(fc_fname, fc_isdir, true, fc_elevel);
}

static void fc_unlink_if_exists_fname(const char *fc_fname, bool fc_isdir, int fc_elevel)
{
	if (fc_isdir)
	{
		if (rmdir(fc_fname) != 0 && errno != ENOENT)
			ereport(fc_elevel,
					(errcode_for_file_access(),
					 errmsg("could not remove directory \"%s\": %m", fc_fname)));
	}
	else
	{
		/* 使用 PathNameDeleteTemporaryFile 报告文件大小 */
		PathNameDeleteTemporaryFile(fc_fname, false);
	}
}

/*
 * fsync_fname_ext -- 尝试对文件或目录进行 fsync
 *
 * 如果 ignore_perm 为真，则在尝试打开不可读文件时忽略错误。在调用者指定的级别记录其他错误。
 *
 * 如果操作成功，返回 0；否则返回 -1。
 */
int fsync_fname_ext(const char *fc_fname, bool fc_isdir, bool fc_ignore_perm, int fc_elevel)
{
	int			fc_fd;
	int			fc_flags;
	int			fc_returncode;

	/*
	 * 一些操作系统要求目录以只读方式打开，而另一些系统不允许我们对以只读方式打开的文件进行 fsync；因此我们这里需要考虑这两种情况。使用 O_RDWR 将导致我们无法对用户ID没有写权限的文件进行 fsync，但我们认为这没问题。
	 */
	fc_flags = PG_BINARY;
	if (!fc_isdir)
		fc_flags |= O_RDWR;
	else
		fc_flags |= O_RDONLY;

	fc_fd = OpenTransientFile(fc_fname, fc_flags);

	/*
	 * 某些操作系统根本不允许我们打开目录（Windows 返回 EACCES），在这种情况下忽略该错误。
	 * 如果需要，还可以悄悄地忽略关于不可读文件的错误。记录其他错误。
	 */
	if (fc_fd < 0 && fc_isdir && (errno == EISDIR || errno == EACCES))
		return 0;
	else if (fc_fd < 0 && fc_ignore_perm && errno == EACCES)
		return 0;
	else if (fc_fd < 0)
	{
		ereport(fc_elevel,
				(errcode_for_file_access(),
				 errmsg("could not open file \"%s\": %m", fc_fname)));
		return -1;
	}

	fc_returncode = pg_fsync(fc_fd);

	/*
	 * 一些操作系统根本不允许我们对目录进行 fsync，因此我们可以忽略这些错误。
	 * 其他任何错误都需要记录。
	 */
	if (fc_returncode != 0 && !(fc_isdir && (errno == EBADF || errno == EINVAL)))
	{
		int			fc_save_errno;

		/* 在出错时关闭文件，可能不在事务上下文中 */
		fc_save_errno = errno;
		(void) CloseTransientFile(fc_fd);
		errno = fc_save_errno;

		ereport(fc_elevel,
				(errcode_for_file_access(),
				 errmsg("could not fsync file \"%s\": %m", fc_fname)));
		return -1;
	}

	if (CloseTransientFile(fc_fd) != 0)
	{
		ereport(fc_elevel,
				(errcode_for_file_access(),
				 errmsg("could not close file \"%s\": %m", fc_fname)));
		return -1;
	}

	return 0;
}

/*
 * fsync_parent_path -- 对文件或目录的父路径进行 fsync
 *
 * 这旨在确保在操作系统崩溃或电源故障时，文件操作在磁盘上是持久的。
 */
static int fc_fsync_parent_path(const char *fc_fname, int fc_elevel)
{
	char		fc_parentpath[MAXPGPATH];

	strlcpy(fc_parentpath, fc_fname, MAXPGPATH);
	get_parent_directory(fc_parentpath);

	/*
	 * get_parent_directory() 如果输入参数只是文件名，则返回空字符串（参见 path.c 中的注释），因此将其视为当前目录。
	 */
	if (strlen(fc_parentpath) == 0)
		strlcpy(fc_parentpath, ".", MAXPGPATH);

	if (fsync_fname_ext(fc_parentpath, true, false, fc_elevel) != 0)
		return -1;

	return 0;
}

/*
 * 创建 PostgreSQL 数据子目录
 *
 * 数据目录本身及其大多数子目录在 initdb 时创建，但我们确实有一些在后端创建目录的场合（例如 CREATE TABLESPACE）。
 * 在这些情况下，我们想确保这些目录的一致性。今天，这意味着确保创建的目录具有正确的权限，这正是 pg_dir_create_mode 为我们跟踪的内容。
 *
 * 请注意，我们还根据我们理解的正确权限设置 umask()（参见 file_perm.c）。
 *
 * 除了默认权限外，可以直接使用 mkdir()，但请确保仔细考虑此类情况 - 在 PostgreSQL 数据目录中具有不正确权限的子目录可能导致备份和其他过程失败。
 */
int MakePGDirectory(const char *fc_directoryName)
{
	return mkdir(fc_directoryName, pg_dir_create_mode);
}

/*
 * 返回传入的错误级别，如果 data_sync_retry 关闭则返回 PANIC。
 *
 * 对任何数据文件进行 fsync 失败立即导致恐慌，除非启用了 data_sync_retry。
 * 数据可能已经被写入操作系统并从我们的缓冲池中移除，如果我们在一个会忘记脏数据的操作系统上运行，可能只剩下一个副本的数据：在 WAL 中。
 * 随后再次尝试 fsync 可能错误地报告成功。
 * 因此，我们必须不允许进行任何进一步的检查点。理论上可以在已知不丢弃脏缓冲数据的操作系统上启用 data_sync_retry（可能导致检查点将在底层问题解决之前持续失败）。
 *
 * 任何报告 fsync() 或相关函数失败的代码应使用此函数过滤错误级别。
 */
int data_sync_elevel(int fc_elevel)
{
	return data_sync_retry ? fc_elevel : PANIC;
}

/*
 * pg_pwritev() 的便利包装器，在部分写入时重试。如果返回错误，已写入的内容数量不确定。
 */
ssize_t pg_pwritev_with_retry(int fc_fd, const struct iovec *fc_iov, int fc_iovcnt, off_t fc_offset)
{
	struct iovec fc_iov_copy[PG_IOV_MAX];
	ssize_t		fc_sum = 0;
	ssize_t		fc_part;

	/* 我们最好有空间进行复制，以防我们需要重试。 */
	if (fc_iovcnt > PG_IOV_MAX)
	{
		errno = EINVAL;
		return -1;
	}

	for (;;)
	{
		/* 尽可能多地写入。 */
		fc_part = pg_pwritev(fc_fd, fc_iov, fc_iovcnt, fc_offset);
		if (fc_part < 0)
			return -1;

#ifdef SIMULATE_SHORT_WRITE
		fc_part = Min(fc_part, 4096);
#endif

		/* 统计我们的进展。 */
		fc_sum += fc_part;
		fc_offset += fc_part;

		/* 跳过已完成的 iovecs。 */
		while (fc_iovcnt > 0 && fc_iov->iov_len <= fc_part)
		{
			fc_part -= fc_iov->iov_len;
			++fc_iov;
			--fc_iovcnt;
		}

		/* 他们都完成了吗？ */
		if (fc_iovcnt == 0)
		{
			/* 我们不期望内核写入超过请求的内容。 */
			Assert(fc_part == 0);
			break;
		}

		/*
 * 将剩下的内容移动到我们可变副本的前面并调整前导 iovec。
 */
		Assert(fc_iovcnt > 0);
		memmove(fc_iov_copy, fc_iov, sizeof(*fc_iov) * fc_iovcnt);
		Assert(fc_iov->iov_len > fc_part);
		fc_iov_copy[0].iov_base = (char *) fc_iov_copy[0].iov_base + fc_part;
		fc_iov_copy[0].iov_len -= fc_part;
		fc_iov = fc_iov_copy;
	}

	return fc_sum;
}
